From 8bdbe7aae06a13d60030020cb2c37cb50c52b7e6 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Wed, 1 Apr 2015 13:48:32 +0200 Subject: [PATCH 01/88] Add earcut to package.json and wrap it in ol.ext Earcut is a library for triangulating polygons. See https://github.com/mapbox/earcut. --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0613b80f49..73eaacd414 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "browserify": "13.1.1", "closure-util": "1.15.1", "derequire": "2.0.3", + "earcut": "^1.4.2", "fs-extra": "1.0.0", "glob": "7.1.1", "handlebars": "4.0.6", @@ -123,6 +124,7 @@ "module": "vector-tile", "name": "vectortile", "browserify": true - } + }, + "earcut" ] } From 2519cf36fca2000ecec9cd9c114953ba3c35384d Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 2 Apr 2015 13:51:39 +0200 Subject: [PATCH 02/88] Basic WEBGL rendering of polygons --- src/ol/render/webgl/imagereplay/index.js | 315 +++++++++++++++++- src/ol/render/webgl/webglpolygondefault.glsl | 22 ++ .../render/webgl/webglpolygondefaultshader.js | 99 ++++++ 3 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 src/ol/render/webgl/webglpolygondefault.glsl create mode 100644 src/ol/render/webgl/webglpolygondefaultshader.js diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index ff0a89e4ed..1953fba331 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1,7 +1,9 @@ goog.provide('ol.render.webgl.ImageReplay'); +goog.provide('ol.render.webgl.PolygonReplay'); goog.provide('ol.render.webgl.ReplayGroup'); goog.require('ol'); +goog.require('ol.ext.earcut'); goog.require('ol.extent'); goog.require('ol.obj'); goog.require('ol.render.ReplayGroup'); @@ -9,6 +11,7 @@ goog.require('ol.render.VectorContext'); goog.require('ol.render.replay'); goog.require('ol.render.webgl.imagereplay.defaultshader'); goog.require('ol.transform'); +goog.require('ol.render.webgl.polygonreplay.shader.Default'); goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); @@ -905,6 +908,315 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { }; +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @protected + * @struct + */ +ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { + goog.base(this); + + /** + * @private + * @type {ol.color.Matrix} + */ + this.colorMatrix_ = new ol.color.Matrix(); + + /** + * The origin of the coordinate system for the point coordinates sent to + * the GPU. + * @private + * @type {ol.Coordinate} + */ + this.origin_ = ol.extent.getCenter(maxExtent); + + /** + * @type {Array.} + * @private + */ + this.indices_ = []; + + /** + * @type {ol.webgl.Buffer} + * @private + */ + this.indicesBuffer_ = null; + + /** + * @private + * @type {ol.render.webgl.polygonreplay.shader.Default.Locations} + */ + this.defaultLocations_ = null; + + /** + * @type {!goog.vec.Mat4.Number} + * @private + */ + this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity(); + + /** + * @type {Array.} + * @private + */ + this.vertices_ = []; + + /** + * @type {ol.webgl.Buffer} + * @private + */ + this.verticesBuffer_ = null; + + /** + * Start index per feature (the index). + * @type {Array.} + * @private + */ + this.startIndices_ = []; + + /** + * Start index per feature (the feature). + * @type {Array.} + * @private + */ + this.startIndicesFeature_ = []; +}; +goog.inherits(ol.render.webgl.PolygonReplay, ol.render.VectorContext); + + +/** + * Draw one polygon. + * @param {Array.>} coordinates + * @private + */ +ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = + function(coordinates) { + var triangulation = ol.ext.earcut(coordinates, true); + var offset = this.vertices_.length / 2; + if (offset === 0) { + this.indices_ = triangulation.indices; + this.vertices_ = triangulation.vertices; + } else { + var i, ii; + var indices = triangulation.indices; + for (i = 0, ii = indices.length; i < ii; ++i) { + this.indices_.push(indices[i] + offset); + } + var vertices = triangulation.vertices; + for (i = 0, ii = vertices.length; i < ii; ++i) { + this.vertices_.push(vertices[i]); + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawLineStringGeometry = + goog.abstractMethod; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawMultiLineStringGeometry = + goog.abstractMethod; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawMultiPolygonGeometry = + function(geometry, feature) { + var coordinatess = geometry.getCoordinates(); + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); + var i, ii; + for (i = 0, ii = coordinatess.length; i < ii; i++) { + this.drawCoordinates_(coordinatess[i]); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawPolygonGeometry = + function(polygonGeometry, feature) { + var coordinates = polygonGeometry.getCoordinates(); + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); + this.drawCoordinates_(coordinates); +}; + + +/** + * @param {ol.webgl.Context} context Context. + **/ +ol.render.webgl.PolygonReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); + context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + + var indices = this.indices_; + var bits = context.hasOESElementIndexUint ? 32 : 16; + goog.asserts.assert(indices[indices.length - 1] < Math.pow(2, bits), + 'Too large element index detected [%s] (OES_element_index_uint "%s")', + indices[indices.length - 1], context.hasOESElementIndexUint); + + // create, bind, and populate the indices buffer + this.indicesBuffer_ = new ol.webgl.Buffer(indices); + context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); +}; + + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = + function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other PolygonReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + goog.asserts.assert(!goog.isNull(this.verticesBuffer_), + 'verticesBuffer must not be null'); + goog.asserts.assert(!goog.isNull(this.indicesBuffer_), + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer_; + var indicesBuffer = this.indicesBuffer_; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.PolygonReplay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + var gl = context.getGL(); + + // bind the vertices buffer + goog.asserts.assert(!goog.isNull(this.verticesBuffer_), + 'verticesBuffer must not be null'); + context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + + // bind the indices buffer + goog.asserts.assert(!goog.isNull(this.indicesBuffer_), + 'indicesBuffer must not be null'); + context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + + // get the program + var fragmentShader, vertexShader; + fragmentShader = + ol.render.webgl.polygonreplay.shader.DefaultFragment.getInstance(); + vertexShader = + ol.render.webgl.polygonreplay.shader.DefaultVertex.getInstance(); + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (goog.isNull(this.defaultLocations_)) { + locations = + new ol.render.webgl.polygonreplay.shader.Default.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, + false, 8, 0); + + + // set the "uniform" values + // TODO: use RTE to avoid jitter + var projectionMatrix = this.projectionMatrix_; + ol.vec.Mat4.makeTransform2D(projectionMatrix, + 0.0, 0.0, + 2 / (resolution * size[0]), + 2 / (resolution * size[1]), + -rotation, + -center[0], -center[1]); + + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, projectionMatrix); + + // draw! + var result; + if (!goog.isDef(featureCallback)) { + this.drawReplay_(gl, context, skippedFeaturesHash); + } else { + // TODO: draw feature by feature for the hit-detection + } + + // disable the vertex attrib arrays + gl.disableVertexAttribArray(locations.a_position); + + return result; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.PolygonReplay.prototype.drawReplay_ = + function(gl, context, skippedFeaturesHash) { + var elementType = context.hasOESElementIndexUint ? + goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; + // var elementSize = context.hasOESElementIndexUint ? 4 : 2; + + if (!goog.object.isEmpty(skippedFeaturesHash)) { + // TODO: draw by blocks to skip features + } else { + var numItems = this.indices_.length; + gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, 0); + } +}; + + +/** + * @inheritDoc + */ + +ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = + function(fillStyle, strokeStyle) { + // TODO implement +}; + + + /** * @constructor * @extends {ol.render.ReplayGroup} @@ -1166,7 +1478,8 @@ ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( * ol.Extent)>} */ ol.render.webgl.BATCH_CONSTRUCTORS_ = { - 'Image': ol.render.webgl.ImageReplay + 'Image': ol.render.webgl.ImageReplay, + 'Polygon': ol.render.webgl.PolygonReplay }; diff --git a/src/ol/render/webgl/webglpolygondefault.glsl b/src/ol/render/webgl/webglpolygondefault.glsl new file mode 100644 index 0000000000..1a29c4cf5d --- /dev/null +++ b/src/ol/render/webgl/webglpolygondefault.glsl @@ -0,0 +1,22 @@ +//! NAMESPACE=ol.render.webgl.polygonreplay.shader.Default +//! CLASS=ol.render.webgl.polygonreplay.shader.Default + + +//! COMMON + + +//! VERTEX +attribute vec2 a_position; + +uniform mat4 u_projectionMatrix; + +void main(void) { + gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.); +} + + +//! FRAGMENT + +void main(void) { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); +} diff --git a/src/ol/render/webgl/webglpolygondefaultshader.js b/src/ol/render/webgl/webglpolygondefaultshader.js new file mode 100644 index 0000000000..80b5c760b3 --- /dev/null +++ b/src/ol/render/webgl/webglpolygondefaultshader.js @@ -0,0 +1,99 @@ +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.polygonreplay.shader.Default'); + +goog.require('ol.webgl.shader'); + + + +/** + * @constructor + * @extends {ol.webgl.shader.Fragment} + * @struct + */ +ol.render.webgl.polygonreplay.shader.DefaultFragment = function() { + goog.base(this, ol.render.webgl.polygonreplay.shader.DefaultFragment.SOURCE); +}; +goog.inherits(ol.render.webgl.polygonreplay.shader.DefaultFragment, ol.webgl.shader.Fragment); +goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultFragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nvoid main(void) {\n gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;void main(void){gl_FragColor=vec4(1.0,1.0,1.0,1.0);}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? + ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE; + + + +/** + * @constructor + * @extends {ol.webgl.shader.Vertex} + * @struct + */ +ol.render.webgl.polygonreplay.shader.DefaultVertex = function() { + goog.base(this, ol.render.webgl.polygonreplay.shader.DefaultVertex.SOURCE); +}; +goog.inherits(ol.render.webgl.polygonreplay.shader.DefaultVertex, ol.webgl.shader.Vertex); +goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultVertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;void main(void){gl_Position=b*vec4(a,0.,1.);}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? + ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE; + + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.polygonreplay.shader.Default.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_projectionMatrix' : 'b'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, goog.DEBUG ? 'a_position' : 'a'); +}; From 581ea8c77507f0841b93e80f53b607a0b94a7749 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 2 Apr 2015 19:11:46 +0200 Subject: [PATCH 03/88] Add color to WEBGL polygons --- src/ol/render/webgl/imagereplay/index.js | 63 +++++++++++++------ src/ol/render/webgl/webglpolygondefault.glsl | 5 +- .../render/webgl/webglpolygondefaultshader.js | 18 ++++-- 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 1953fba331..659e552183 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -3,6 +3,7 @@ goog.provide('ol.render.webgl.PolygonReplay'); goog.provide('ol.render.webgl.ReplayGroup'); goog.require('ol'); +goog.require('ol.color'); goog.require('ol.ext.earcut'); goog.require('ol.extent'); goog.require('ol.obj'); @@ -921,9 +922,10 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { /** * @private - * @type {ol.color.Matrix} + * @type {ol.Color} */ - this.colorMatrix_ = new ol.color.Matrix(); + this.fillColor_ = null; + /** * The origin of the coordinate system for the point coordinates sent to @@ -993,21 +995,27 @@ goog.inherits(ol.render.webgl.PolygonReplay, ol.render.VectorContext); */ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(coordinates) { + // Triangulate the polgon var triangulation = ol.ext.earcut(coordinates, true); - var offset = this.vertices_.length / 2; - if (offset === 0) { - this.indices_ = triangulation.indices; - this.vertices_ = triangulation.vertices; - } else { - var i, ii; - var indices = triangulation.indices; - for (i = 0, ii = indices.length; i < ii; ++i) { - this.indices_.push(indices[i] + offset); - } - var vertices = triangulation.vertices; - for (i = 0, ii = vertices.length; i < ii; ++i) { - this.vertices_.push(vertices[i]); - } + var i, ii; + var indices = triangulation.indices; + + // Shift the indices to take into account previously handled polygons + var offset = this.vertices_.length / 6; + for (i = 0, ii = indices.length; i < ii; ++i) { + this.indices_.push(indices[i] + offset); + } + + // Add the color property to each vertex + // TODO performance: make it more efficient + var vertices = triangulation.vertices; + for (i = 0, ii = vertices.length / 2; i < ii; ++i) { + this.vertices_.push(vertices[2 * i]); + this.vertices_.push(vertices[2 * i + 1]); + this.vertices_.push(this.fillColor_[0]); + this.vertices_.push(this.fillColor_[1]); + this.vertices_.push(this.fillColor_[2]); + this.vertices_.push(this.fillColor_[3]); } }; @@ -1031,6 +1039,9 @@ ol.render.webgl.PolygonReplay.prototype.drawMultiLineStringGeometry = */ ol.render.webgl.PolygonReplay.prototype.drawMultiPolygonGeometry = function(geometry, feature) { + if (goog.isNull(this.fillColor_)) { + return; + } var coordinatess = geometry.getCoordinates(); this.startIndices_.push(this.indices_.length); this.startIndicesFeature_.push(feature); @@ -1046,6 +1057,9 @@ ol.render.webgl.PolygonReplay.prototype.drawMultiPolygonGeometry = */ ol.render.webgl.PolygonReplay.prototype.drawPolygonGeometry = function(polygonGeometry, feature) { + if (goog.isNull(this.fillColor_)) { + return; + } var coordinates = polygonGeometry.getCoordinates(); this.startIndices_.push(this.indices_.length); this.startIndicesFeature_.push(feature); @@ -1143,8 +1157,8 @@ ol.render.webgl.PolygonReplay.prototype.replay = function(context, // get the locations var locations; if (goog.isNull(this.defaultLocations_)) { - locations = - new ol.render.webgl.polygonreplay.shader.Default.Locations(gl, program); + locations = new ol.render.webgl.polygonreplay.shader.Default + .Locations(gl, program); this.defaultLocations_ = locations; } else { locations = this.defaultLocations_; @@ -1155,8 +1169,11 @@ ol.render.webgl.PolygonReplay.prototype.replay = function(context, // enable the vertex attrib arrays gl.enableVertexAttribArray(locations.a_position); gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, - false, 8, 0); + false, 24, 0); + gl.enableVertexAttribArray(locations.a_color); + gl.vertexAttribPointer(locations.a_color, 4, goog.webgl.FLOAT, + false, 24, 8); // set the "uniform" values // TODO: use RTE to avoid jitter @@ -1180,6 +1197,7 @@ ol.render.webgl.PolygonReplay.prototype.replay = function(context, // disable the vertex attrib arrays gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_color); return result; }; @@ -1213,6 +1231,13 @@ ol.render.webgl.PolygonReplay.prototype.drawReplay_ = ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { // TODO implement + if (!goog.isNull(fillStyle)) { + var fillStyleColor = fillStyle.getColor(); + this.fillColor_ = !goog.isNull(fillStyleColor) ? + ol.color.asArray(fillStyleColor) : [0.0, 0.0, 0.0, 1.0]; + } else { + this.fillColor_ = null; + } }; diff --git a/src/ol/render/webgl/webglpolygondefault.glsl b/src/ol/render/webgl/webglpolygondefault.glsl index 1a29c4cf5d..59712c4228 100644 --- a/src/ol/render/webgl/webglpolygondefault.glsl +++ b/src/ol/render/webgl/webglpolygondefault.glsl @@ -3,14 +3,17 @@ //! COMMON +varying vec4 v_color; //! VERTEX attribute vec2 a_position; +attribute vec4 a_color; uniform mat4 u_projectionMatrix; void main(void) { + v_color = a_color; gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.); } @@ -18,5 +21,5 @@ void main(void) { //! FRAGMENT void main(void) { - gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + gl_FragColor = v_color; } diff --git a/src/ol/render/webgl/webglpolygondefaultshader.js b/src/ol/render/webgl/webglpolygondefaultshader.js index 80b5c760b3..30db27c23e 100644 --- a/src/ol/render/webgl/webglpolygondefaultshader.js +++ b/src/ol/render/webgl/webglpolygondefaultshader.js @@ -21,14 +21,14 @@ goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultFragment); * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nvoid main(void) {\n gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n}\n'; +ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec4 v_color;\n\n\n\nvoid main(void) {\n gl_FragColor = v_color;\n}\n'; /** * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;void main(void){gl_FragColor=vec4(1.0,1.0,1.0,1.0);}'; +ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec4 a;void main(void){gl_FragColor=a;}'; /** @@ -57,14 +57,14 @@ goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n}\n\n\n'; +ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec4 v_color;\n\n\nattribute vec2 a_position;\nattribute vec4 a_color;\n\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n v_color = a_color;\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;void main(void){gl_Position=b*vec4(a,0.,1.);}'; +ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec4 a;attribute vec2 b;attribute vec4 c;uniform mat4 d;void main(void){a=c;gl_Position=d*vec4(b,0.,1.);}'; /** @@ -89,11 +89,17 @@ ol.render.webgl.polygonreplay.shader.Default.Locations = function(gl, program) { * @type {WebGLUniformLocation} */ this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'b'); + program, goog.DEBUG ? 'u_projectionMatrix' : 'd'); + + /** + * @type {number} + */ + this.a_color = gl.getAttribLocation( + program, goog.DEBUG ? 'a_color' : 'c'); /** * @type {number} */ this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'a'); + program, goog.DEBUG ? 'a_position' : 'b'); }; From 8e9b589ca9e4d54952e801332b13e0962983335b Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Fri, 3 Apr 2015 20:25:07 +0200 Subject: [PATCH 04/88] Add tests to PolygonReplay renderer --- test/spec/ol/render/webgl/replay.test.js | 84 ++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/test/spec/ol/render/webgl/replay.test.js b/test/spec/ol/render/webgl/replay.test.js index bf55e7ed20..79eee0e937 100644 --- a/test/spec/ol/render/webgl/replay.test.js +++ b/test/spec/ol/render/webgl/replay.test.js @@ -1,8 +1,13 @@ goog.provide('ol.test.render.webgl.Replay'); +goog.require('ol.extent'); goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.render.webgl.PolygonReplay'); +goog.require('ol.style.Fill'); goog.require('ol.style.Image'); @@ -168,3 +173,82 @@ describe('ol.render.webgl.ImageReplay', function() { }); }); }); + + +describe('ol.render.webgl.PolygonReplay', function() { + var replay; + + var fillStyle = new ol.style.Fill({ + color: [0, 0, 255, 0.5] + }); + + beforeEach(function() { + var tolerance = 0.1; + var maxExtent = [-10000, -20000, 10000, 20000]; + replay = new ol.render.webgl.PolygonReplay(tolerance, maxExtent); + }); + + describe('#drawPolygonGeometry', function() { + beforeEach(function() { + replay.setFillStrokeStyle(fillStyle, null); + }); + + it('sets the buffer data', function() { + var polygon1 = new ol.geom.Polygon( + [[[1000, 2000], [1200, 2000], [1200, 3000]]] + ); + replay.drawPolygonGeometry(polygon1, null); + expect(replay.vertices_).to.have.length(18); + expect(replay.indices_).to.have.length(3); + + expect(replay.vertices_).to.eql([ + 1200, 2000, 0, 0, 255, 0.5, + 1200, 3000, 0, 0, 255, 0.5, + 1000, 2000, 0, 0, 255, 0.5]); + expect(replay.indices_).to.eql([0, 1, 2]); + + var polygon2 = new ol.geom.Polygon( + [[[4000, 2000], [4200, 2000], [4200, 3000]]] + ); + replay.drawPolygonGeometry(polygon2, null); + expect(replay.vertices_).to.have.length(36); + expect(replay.indices_).to.have.length(6); + + expect(replay.vertices_).to.eql([ + 1200, 2000, 0, 0, 255, 0.5, + 1200, 3000, 0, 0, 255, 0.5, + 1000, 2000, 0, 0, 255, 0.5, + 4200, 2000, 0, 0, 255, 0.5, + 4200, 3000, 0, 0, 255, 0.5, + 4000, 2000, 0, 0, 255, 0.5 + ]); + expect(replay.indices_).to.eql([0, 1, 2, 3, 4, 5]); + }); + }); + + describe('#drawMultiPolygonGeometry', function() { + beforeEach(function() { + replay.setFillStrokeStyle(fillStyle, null); + }); + + it('sets the buffer data', function() { + var multiPolygon = new ol.geom.MultiPolygon([ + [[[1000, 2000], [1200, 2000], [1200, 3000]]], + [[[4000, 2000], [4200, 2000], [4200, 3000]]] + ]); + replay.drawMultiPolygonGeometry(multiPolygon, null); + expect(replay.vertices_).to.have.length(36); + expect(replay.indices_).to.have.length(6); + + expect(replay.vertices_).to.eql([ + 1200, 2000, 0, 0, 255, 0.5, + 1200, 3000, 0, 0, 255, 0.5, + 1000, 2000, 0, 0, 255, 0.5, + 4200, 2000, 0, 0, 255, 0.5, + 4200, 3000, 0, 0, 255, 0.5, + 4000, 2000, 0, 0, 255, 0.5 + ]); + expect(replay.indices_).to.eql([0, 1, 2, 3, 4, 5]); + }); + }); +}); From e40b545e40bb4c77d39ef5512d71990fa29ff71b Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Sat, 4 Apr 2015 22:42:40 +0200 Subject: [PATCH 05/88] Add basic WEBGL LineString rendering Use gl.LINES and hardcode a width. Implement polygon stroking using the line string replay. --- src/ol/render/webgl/imagereplay/index.js | 308 +++++++++++++++++++++-- test/spec/ol/render/webgl/replay.test.js | 46 ++-- 2 files changed, 320 insertions(+), 34 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 659e552183..f59a072fe4 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1,4 +1,5 @@ goog.provide('ol.render.webgl.ImageReplay'); +goog.provide('ol.render.webgl.LineStringReplay'); goog.provide('ol.render.webgl.PolygonReplay'); goog.provide('ol.render.webgl.ReplayGroup'); @@ -909,6 +910,267 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { }; +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @protected + * @struct + */ +ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { + goog.base(this); + + /** + * @private + * @type {ol.Color} + */ + this.strokeColor_ = null; + + + /** + * The origin of the coordinate system for the point coordinates sent to + * the GPU. + * @private + * @type {ol.Coordinate} + */ + this.origin_ = ol.extent.getCenter(maxExtent); + + + /** + * @private + * @type {ol.render.webgl.polygonreplay.shader.Default.Locations} + */ + this.defaultLocations_ = null; + + /** + * @type {!goog.vec.Mat4.Number} + * @private + */ + this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity(); + + /** + * @type {Array.} + * @private + */ + this.vertices_ = []; + + /** + * @type {ol.webgl.Buffer} + * @private + */ + this.verticesBuffer_ = null; +}; +goog.inherits(ol.render.webgl.LineStringReplay, ol.render.VectorContext); + + +/** + * Draw one line. + * @param {Array.} coordinates + * @private + */ +ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = + function(coordinates) { + var i, ii; + + // Shift the indices to take into account previously handled lines + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + var point1 = coordinates[i]; + this.vertices_.push(point1[0]); + this.vertices_.push(point1[1]); + this.vertices_.push(this.strokeColor_[0]); + this.vertices_.push(this.strokeColor_[1]); + this.vertices_.push(this.strokeColor_[2]); + this.vertices_.push(this.strokeColor_[3]); + + var point2 = coordinates[i + 1]; + this.vertices_.push(point2[0]); + this.vertices_.push(point2[1]); + this.vertices_.push(this.strokeColor_[0]); + this.vertices_.push(this.strokeColor_[1]); + this.vertices_.push(this.strokeColor_[2]); + this.vertices_.push(this.strokeColor_[3]); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawLineStringGeometry = + function(geometry, feature) { + this.drawCoordinates_(geometry.getCoordinates()); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawMultiLineStringGeometry = + function(geometry, feature) { + var coordinatess = geometry.getCoordinates(); + var i, ii; + for (i = 0, ii = coordinatess.length; i < ii; ++i) { + this.drawCoordinates_(coordinatess[i]); + } +}; + + +/** + * @param {ol.webgl.Context} context Context. + **/ +ol.render.webgl.LineStringReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); + context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); +}; + + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = + function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other LineStringReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + goog.asserts.assert(!goog.isNull(this.verticesBuffer_), + 'verticesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer_; + return function() { + context.deleteBuffer(verticesBuffer); + }; +}; + + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.LineStringReplay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + var gl = context.getGL(); + + // bind the vertices buffer + goog.asserts.assert(!goog.isNull(this.verticesBuffer_), + 'verticesBuffer must not be null'); + context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + + // get the program + var fragmentShader, vertexShader; + fragmentShader = + ol.render.webgl.polygonreplay.shader.DefaultFragment.getInstance(); + vertexShader = + ol.render.webgl.polygonreplay.shader.DefaultVertex.getInstance(); + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (goog.isNull(this.defaultLocations_)) { + locations = new ol.render.webgl.polygonreplay.shader.Default + .Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, + false, 24, 0); + + gl.enableVertexAttribArray(locations.a_color); + gl.vertexAttribPointer(locations.a_color, 4, goog.webgl.FLOAT, + false, 24, 8); + + // set the "uniform" values + // TODO: use RTE to avoid jitter + var projectionMatrix = this.projectionMatrix_; + ol.vec.Mat4.makeTransform2D(projectionMatrix, + 0.0, 0.0, + pixelRatio * 2 / (resolution * size[0]), + pixelRatio * 2 / (resolution * size[1]), + -rotation, + -center[0], -center[1]); + + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, projectionMatrix); + + // draw! + var result; + if (!goog.isDef(featureCallback)) { + this.drawReplay_(gl, context, skippedFeaturesHash); + } else { + // TODO: draw feature by feature for the hit-detection + } + + // disable the vertex attrib arrays + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_color); + + return result; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.LineStringReplay.prototype.drawReplay_ = + function(gl, context, skippedFeaturesHash) { + if (!goog.object.isEmpty(skippedFeaturesHash)) { + // TODO: draw by blocks to skip features + } else { + var numItems = this.vertices_.length / 6; + // FIXME: not compatible with batching, hardcoding some arbitrary value + gl.lineWidth(3); + gl.drawArrays(goog.webgl.LINES, 0, numItems); + gl.lineWidth(1); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = + function(fillStyle, strokeStyle) { + if (!goog.isNull(strokeStyle)) { + var strokeStyleColor = strokeStyle.getColor(); + this.strokeColor_ = !goog.isNull(strokeStyleColor) ? + ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) : [0.0, 0.0, 0.0, 1.0]; + } else { + this.strokeColor_ = null; + } +}; + + + /** * @constructor * @extends {ol.render.VectorContext} @@ -927,6 +1189,12 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { this.fillColor_ = null; + /** + * @private + */ + this.lineStringReplay_ = new ol.render.webgl.LineStringReplay( + tolerance, maxExtent); + /** * The origin of the coordinate system for the point coordinates sent to * the GPU. @@ -1020,20 +1288,6 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = }; -/** - * @inheritDoc - */ -ol.render.webgl.PolygonReplay.prototype.drawLineStringGeometry = - goog.abstractMethod; - - -/** - * @inheritDoc - */ -ol.render.webgl.PolygonReplay.prototype.drawMultiLineStringGeometry = - goog.abstractMethod; - - /** * @inheritDoc */ @@ -1064,6 +1318,14 @@ ol.render.webgl.PolygonReplay.prototype.drawPolygonGeometry = this.startIndices_.push(this.indices_.length); this.startIndicesFeature_.push(feature); this.drawCoordinates_(coordinates); + + if (!goog.isNull(this.lineStringReplay_.strokeColor_)) { + var linearRings = polygonGeometry.getLinearRings(); + var i, ii; + for (i = 0, ii = linearRings.length; i < ii; i++) { + this.lineStringReplay_.drawCoordinates_(linearRings[i].getCoordinates()); + } + } }; @@ -1084,6 +1346,7 @@ ol.render.webgl.PolygonReplay.prototype.finish = function(context) { // create, bind, and populate the indices buffer this.indicesBuffer_ = new ol.webgl.Buffer(indices); context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + this.lineStringReplay_.finish(context); }; @@ -1103,9 +1366,11 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = 'indicesBuffer must not be null'); var verticesBuffer = this.verticesBuffer_; var indicesBuffer = this.indicesBuffer_; + var lineDeleter = this.lineStringReplay_.getDeleteResourcesFunction(context); return function() { context.deleteBuffer(verticesBuffer); context.deleteBuffer(indicesBuffer); + lineDeleter(); }; }; @@ -1180,8 +1445,8 @@ ol.render.webgl.PolygonReplay.prototype.replay = function(context, var projectionMatrix = this.projectionMatrix_; ol.vec.Mat4.makeTransform2D(projectionMatrix, 0.0, 0.0, - 2 / (resolution * size[0]), - 2 / (resolution * size[1]), + pixelRatio * 2 / (resolution * size[0]), + pixelRatio * 2 / (resolution * size[1]), -rotation, -center[0], -center[1]); @@ -1199,6 +1464,11 @@ ol.render.webgl.PolygonReplay.prototype.replay = function(context, gl.disableVertexAttribArray(locations.a_position); gl.disableVertexAttribArray(locations.a_color); + this.lineStringReplay_.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + // FIXME get result return result; }; @@ -1234,10 +1504,13 @@ ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = if (!goog.isNull(fillStyle)) { var fillStyleColor = fillStyle.getColor(); this.fillColor_ = !goog.isNull(fillStyleColor) ? - ol.color.asArray(fillStyleColor) : [0.0, 0.0, 0.0, 1.0]; + ol.color.asArray(fillStyleColor).map(function(c, i) { + return i != 3 ? c / 255.0 : c; + }) : [0.0, 0.0, 0.0, 1.0]; } else { this.fillColor_ = null; } + this.lineStringReplay_.setFillStrokeStyle(fillStyle, strokeStyle); }; @@ -1504,6 +1777,7 @@ ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( */ ol.render.webgl.BATCH_CONSTRUCTORS_ = { 'Image': ol.render.webgl.ImageReplay, + 'LineString': ol.render.webgl.LineStringReplay, 'Polygon': ol.render.webgl.PolygonReplay }; diff --git a/test/spec/ol/render/webgl/replay.test.js b/test/spec/ol/render/webgl/replay.test.js index 79eee0e937..ed5d61f50d 100644 --- a/test/spec/ol/render/webgl/replay.test.js +++ b/test/spec/ol/render/webgl/replay.test.js @@ -9,6 +9,7 @@ goog.require('ol.render.webgl.ImageReplay'); goog.require('ol.render.webgl.PolygonReplay'); goog.require('ol.style.Fill'); goog.require('ol.style.Image'); +goog.require('ol.style.Stroke'); describe('ol.render.webgl.ImageReplay', function() { @@ -181,6 +182,9 @@ describe('ol.render.webgl.PolygonReplay', function() { var fillStyle = new ol.style.Fill({ color: [0, 0, 255, 0.5] }); + var strokeStyle = new ol.style.Stroke({ + color: [0, 255, 0, 0.4] + }); beforeEach(function() { var tolerance = 0.1; @@ -190,7 +194,7 @@ describe('ol.render.webgl.PolygonReplay', function() { describe('#drawPolygonGeometry', function() { beforeEach(function() { - replay.setFillStrokeStyle(fillStyle, null); + replay.setFillStrokeStyle(fillStyle, strokeStyle); }); it('sets the buffer data', function() { @@ -202,11 +206,19 @@ describe('ol.render.webgl.PolygonReplay', function() { expect(replay.indices_).to.have.length(3); expect(replay.vertices_).to.eql([ - 1200, 2000, 0, 0, 255, 0.5, - 1200, 3000, 0, 0, 255, 0.5, - 1000, 2000, 0, 0, 255, 0.5]); + 1200, 2000, 0, 0, 1, 0.5, + 1200, 3000, 0, 0, 1, 0.5, + 1000, 2000, 0, 0, 1, 0.5]); expect(replay.indices_).to.eql([0, 1, 2]); + expect(replay.lineStringReplay_.vertices_).to.have.length(24); + expect(replay.lineStringReplay_.vertices_).to.eql([ + 1000, 2000, 0, 1, 0, 0.4, + 1200, 2000, 0, 1, 0, 0.4, + 1200, 2000, 0, 1, 0, 0.4, + 1200, 3000, 0, 1, 0, 0.4 + ]); + var polygon2 = new ol.geom.Polygon( [[[4000, 2000], [4200, 2000], [4200, 3000]]] ); @@ -215,12 +227,12 @@ describe('ol.render.webgl.PolygonReplay', function() { expect(replay.indices_).to.have.length(6); expect(replay.vertices_).to.eql([ - 1200, 2000, 0, 0, 255, 0.5, - 1200, 3000, 0, 0, 255, 0.5, - 1000, 2000, 0, 0, 255, 0.5, - 4200, 2000, 0, 0, 255, 0.5, - 4200, 3000, 0, 0, 255, 0.5, - 4000, 2000, 0, 0, 255, 0.5 + 1200, 2000, 0, 0, 1, 0.5, + 1200, 3000, 0, 0, 1, 0.5, + 1000, 2000, 0, 0, 1, 0.5, + 4200, 2000, 0, 0, 1, 0.5, + 4200, 3000, 0, 0, 1, 0.5, + 4000, 2000, 0, 0, 1, 0.5 ]); expect(replay.indices_).to.eql([0, 1, 2, 3, 4, 5]); }); @@ -228,7 +240,7 @@ describe('ol.render.webgl.PolygonReplay', function() { describe('#drawMultiPolygonGeometry', function() { beforeEach(function() { - replay.setFillStrokeStyle(fillStyle, null); + replay.setFillStrokeStyle(fillStyle, strokeStyle); }); it('sets the buffer data', function() { @@ -241,12 +253,12 @@ describe('ol.render.webgl.PolygonReplay', function() { expect(replay.indices_).to.have.length(6); expect(replay.vertices_).to.eql([ - 1200, 2000, 0, 0, 255, 0.5, - 1200, 3000, 0, 0, 255, 0.5, - 1000, 2000, 0, 0, 255, 0.5, - 4200, 2000, 0, 0, 255, 0.5, - 4200, 3000, 0, 0, 255, 0.5, - 4000, 2000, 0, 0, 255, 0.5 + 1200, 2000, 0, 0, 1, 0.5, + 1200, 3000, 0, 0, 1, 0.5, + 1000, 2000, 0, 0, 1, 0.5, + 4200, 2000, 0, 0, 1, 0.5, + 4200, 3000, 0, 0, 1, 0.5, + 4000, 2000, 0, 0, 1, 0.5 ]); expect(replay.indices_).to.eql([0, 1, 2, 3, 4, 5]); }); From a052b645dff676145478daf2215c39e5975e064c Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Sat, 14 May 2016 15:22:33 +0200 Subject: [PATCH 06/88] Fix dependencies --- src/ol/render/webgl/imagereplay/index.js | 3 +++ src/ol/render/webgl/webglpolygondefaultshader.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index f59a072fe4..5de00c8ad3 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -14,6 +14,9 @@ goog.require('ol.render.replay'); goog.require('ol.render.webgl.imagereplay.defaultshader'); goog.require('ol.transform'); goog.require('ol.render.webgl.polygonreplay.shader.Default'); +goog.require('ol.render.webgl.polygonreplay.shader.Default.Locations'); +goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); +goog.require('ol.render.webgl.polygonreplay.shader.DefaultVertex'); goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); diff --git a/src/ol/render/webgl/webglpolygondefaultshader.js b/src/ol/render/webgl/webglpolygondefaultshader.js index 30db27c23e..0aa28b8f3d 100644 --- a/src/ol/render/webgl/webglpolygondefaultshader.js +++ b/src/ol/render/webgl/webglpolygondefaultshader.js @@ -1,5 +1,8 @@ // This file is automatically generated, do not edit goog.provide('ol.render.webgl.polygonreplay.shader.Default'); +goog.provide('ol.render.webgl.polygonreplay.shader.Default.Locations'); +goog.provide('ol.render.webgl.polygonreplay.shader.DefaultFragment'); +goog.provide('ol.render.webgl.polygonreplay.shader.DefaultVertex'); goog.require('ol.webgl.shader'); From a6ddee0c5ff62437231e7ee79d927989525d15cc Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Sat, 14 May 2016 17:47:14 +0200 Subject: [PATCH 07/88] Updating old code to current ol3 version --- src/ol/render/vectorcontext.js | 2 +- src/ol/render/webgl/imagereplay/index.js | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ol/render/vectorcontext.js b/src/ol/render/vectorcontext.js index d601ade6d3..26b8fad6ce 100644 --- a/src/ol/render/vectorcontext.js +++ b/src/ol/render/vectorcontext.js @@ -85,7 +85,7 @@ ol.render.VectorContext.prototype.drawMultiPoint = function(multiPointGeometry, /** * @abstract * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.VectorContext.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {}; diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 5de00c8ad3..b3e3c0133e 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1000,7 +1000,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = /** * @inheritDoc */ -ol.render.webgl.LineStringReplay.prototype.drawLineStringGeometry = +ol.render.webgl.LineStringReplay.prototype.drawLineString = function(geometry, feature) { this.drawCoordinates_(geometry.getCoordinates()); }; @@ -1009,7 +1009,7 @@ ol.render.webgl.LineStringReplay.prototype.drawLineStringGeometry = /** * @inheritDoc */ -ol.render.webgl.LineStringReplay.prototype.drawMultiLineStringGeometry = +ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(geometry, feature) { var coordinatess = geometry.getCoordinates(); var i, ii; @@ -1187,9 +1187,9 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { /** * @private - * @type {ol.Color} + * @type {ol.Color|undefined} */ - this.fillColor_ = null; + this.fillColor_ = undefined; /** @@ -1251,7 +1251,7 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { /** * Start index per feature (the feature). - * @type {Array.} + * @type {Array.|Array.} * @private */ this.startIndicesFeature_ = []; @@ -1294,7 +1294,7 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = /** * @inheritDoc */ -ol.render.webgl.PolygonReplay.prototype.drawMultiPolygonGeometry = +ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(geometry, feature) { if (goog.isNull(this.fillColor_)) { return; @@ -1312,7 +1312,7 @@ ol.render.webgl.PolygonReplay.prototype.drawMultiPolygonGeometry = /** * @inheritDoc */ -ol.render.webgl.PolygonReplay.prototype.drawPolygonGeometry = +ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { if (goog.isNull(this.fillColor_)) { return; @@ -1504,14 +1504,14 @@ ol.render.webgl.PolygonReplay.prototype.drawReplay_ = ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { // TODO implement - if (!goog.isNull(fillStyle)) { + if (fillStyle) { var fillStyleColor = fillStyle.getColor(); - this.fillColor_ = !goog.isNull(fillStyleColor) ? + this.fillColor_ = !goog.isNull(fillStyleColor) && Array.isArray(fillStyleColor) ? ol.color.asArray(fillStyleColor).map(function(c, i) { return i != 3 ? c / 255.0 : c; }) : [0.0, 0.0, 0.0, 1.0]; } else { - this.fillColor_ = null; + this.fillColor_ = undefined; } this.lineStringReplay_.setFillStrokeStyle(fillStyle, strokeStyle); }; From f5978c659cae4543889dafb4f186d9547f4c53f8 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 30 May 2016 10:44:25 +0200 Subject: [PATCH 08/88] Adding some defaults --- src/ol/render/webgl/imagereplay/index.js | 13 +++++++------ src/ol/render/webgl/immediate.js | 1 + src/ol/render/webgl/webgl.js | 13 +++++++++++++ src/ol/render/webgl/webgl.jsdoc | 3 +++ 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 src/ol/render/webgl/webgl.js create mode 100644 src/ol/render/webgl/webgl.jsdoc diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index b3e3c0133e..c6d13694f6 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -21,6 +21,7 @@ goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); goog.require('ol.webgl.Context'); +goog.require('ol.render.webgl'); /** @@ -926,9 +927,9 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { /** * @private - * @type {ol.Color} + * @type {ol.Color|undefined} */ - this.strokeColor_ = null; + this.strokeColor_ = undefined; /** @@ -1161,14 +1162,14 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = */ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - if (!goog.isNull(strokeStyle)) { + if (strokeStyle) { var strokeStyleColor = strokeStyle.getColor(); this.strokeColor_ = !goog.isNull(strokeStyleColor) ? ol.color.asArray(strokeStyleColor).map(function(c, i) { return i != 3 ? c / 255 : c; - }) : [0.0, 0.0, 0.0, 1.0]; + }) : ol.render.webgl.defaultStrokeStyle; } else { - this.strokeColor_ = null; + this.strokeColor_ = undefined; } }; @@ -1509,7 +1510,7 @@ ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = this.fillColor_ = !goog.isNull(fillStyleColor) && Array.isArray(fillStyleColor) ? ol.color.asArray(fillStyleColor).map(function(c, i) { return i != 3 ? c / 255.0 : c; - }) : [0.0, 0.0, 0.0, 1.0]; + }) : ol.render.webgl.defaultFillStyle; } else { this.fillColor_ = undefined; } diff --git a/src/ol/render/webgl/immediate.js b/src/ol/render/webgl/immediate.js index 2d52703d85..b7cddd0da2 100644 --- a/src/ol/render/webgl/immediate.js +++ b/src/ol/render/webgl/immediate.js @@ -6,6 +6,7 @@ goog.require('ol.geom.GeometryType'); goog.require('ol.render.ReplayType'); goog.require('ol.render.VectorContext'); goog.require('ol.render.webgl.ReplayGroup'); +goog.require('ol.render.webgl'); /** diff --git a/src/ol/render/webgl/webgl.js b/src/ol/render/webgl/webgl.js new file mode 100644 index 0000000000..4fb7bfe513 --- /dev/null +++ b/src/ol/render/webgl/webgl.js @@ -0,0 +1,13 @@ +goog.provide('ol.render.webgl'); + +/** + * @const + * @type {ol.Color} + */ +ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0]; + +/** + * @const + * @type {ol.Color} + */ +ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0]; diff --git a/src/ol/render/webgl/webgl.jsdoc b/src/ol/render/webgl/webgl.jsdoc new file mode 100644 index 0000000000..2ac3cda5ae --- /dev/null +++ b/src/ol/render/webgl/webgl.jsdoc @@ -0,0 +1,3 @@ +/** + * @namespace ol.render.webgl + */ From 628db9146d6fe372d8d24e39643b50996119fff5 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 1 Jun 2016 14:10:06 +0200 Subject: [PATCH 09/88] Creating a webgl.Replay struct, making the WebGL LineString renderer compilable --- src/ol/render/webgl/imagereplay/index.js | 146 +++++++++++------- src/ol/render/webgl/webgl.js | 6 + .../render/webgl/webglpolygondefaultshader.js | 3 - 3 files changed, 93 insertions(+), 62 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index c6d13694f6..9cc5260ac0 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1,6 +1,7 @@ goog.provide('ol.render.webgl.ImageReplay'); goog.provide('ol.render.webgl.LineStringReplay'); goog.provide('ol.render.webgl.PolygonReplay'); +goog.provide('ol.render.webgl.Replay'); goog.provide('ol.render.webgl.ReplayGroup'); goog.require('ol'); @@ -13,6 +14,7 @@ goog.require('ol.render.VectorContext'); goog.require('ol.render.replay'); goog.require('ol.render.webgl.imagereplay.defaultshader'); goog.require('ol.transform'); +goog.require('ol.render.webgl'); goog.require('ol.render.webgl.polygonreplay.shader.Default'); goog.require('ol.render.webgl.polygonreplay.shader.Default.Locations'); goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); @@ -21,8 +23,6 @@ goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); goog.require('ol.webgl.Context'); -goog.require('ol.render.webgl'); - /** * @constructor @@ -36,16 +36,23 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { ol.render.VectorContext.call(this); /** - * @type {number|undefined} - * @private + * @protected + * @type {number} */ - this.anchorX_ = undefined; + this.tolerance = tolerance; /** - * @type {number|undefined} - * @private + * @protected + * @const + * @type {ol.Extent} */ - this.anchorY_ = undefined; + this.maxExtent = maxExtent; + + /** + * @private + * @type {ol.Extent} + */ + this.bufferedMaxExtent_ = null; /** * The origin of the coordinate system for the point coordinates sent to @@ -57,6 +64,43 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.origin_ = ol.extent.getCenter(maxExtent); + /** + * @type {!goog.vec.Mat4.Number} + * @private + */ + this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity(); +}; +goog.inherits(ol.render.webgl.Replay, ol.render.VectorContext); + +ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = goog.abstractMethod; + +ol.render.webgl.Replay.prototype.finish = goog.abstractMethod; + +ol.render.webgl.Replay.prototype.replay = goog.abstractMethod; + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @protected + * @struct + */ +ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { + goog.base(this, tolerance, maxExtent); + + /** + * @type {number|undefined} + * @private + */ + this.anchorX_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.anchorY_ = undefined; + /** * @type {Array.} * @private @@ -916,14 +960,14 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { /** * @constructor - * @extends {ol.render.VectorContext} + * @extends {ol.render.webgl.Replay} * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. * @protected * @struct */ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { - goog.base(this); + goog.base(this, tolerance, maxExtent); /** * @private @@ -931,28 +975,12 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { */ this.strokeColor_ = undefined; - - /** - * The origin of the coordinate system for the point coordinates sent to - * the GPU. - * @private - * @type {ol.Coordinate} - */ - this.origin_ = ol.extent.getCenter(maxExtent); - - /** * @private * @type {ol.render.webgl.polygonreplay.shader.Default.Locations} */ this.defaultLocations_ = null; - /** - * @type {!goog.vec.Mat4.Number} - * @private - */ - this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity(); - /** * @type {Array.} * @private @@ -965,16 +993,15 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { */ this.verticesBuffer_ = null; }; -goog.inherits(ol.render.webgl.LineStringReplay, ol.render.VectorContext); +goog.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); /** * Draw one line. - * @param {Array.} coordinates + * @param {Array.} coordinates Coordinates. * @private */ -ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = - function(coordinates) { +ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(coordinates) { var i, ii; // Shift the indices to take into account previously handled lines @@ -1001,8 +1028,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = /** * @inheritDoc */ -ol.render.webgl.LineStringReplay.prototype.drawLineString = - function(geometry, feature) { +ol.render.webgl.LineStringReplay.prototype.drawLineString = function(geometry, feature) { this.drawCoordinates_(geometry.getCoordinates()); }; @@ -1010,8 +1036,7 @@ ol.render.webgl.LineStringReplay.prototype.drawLineString = /** * @inheritDoc */ -ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = - function(geometry, feature) { +ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(geometry, feature) { var coordinatess = geometry.getCoordinates(); var i, ii; for (i = 0, ii = coordinatess.length; i < ii; ++i) { @@ -1034,8 +1059,7 @@ ol.render.webgl.LineStringReplay.prototype.finish = function(context) { * @param {ol.webgl.Context} context WebGL context. * @return {function()} Delete resources function. */ -ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = - function(context) { +ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) { // We only delete our stuff here. The shaders and the program may // be used by other LineStringReplay instances (for other layers). And // they will be deleted when disposing of the ol.webgl.Context @@ -1143,8 +1167,7 @@ ol.render.webgl.LineStringReplay.prototype.replay = function(context, * @param {ol.webgl.Context} context Context. * @param {Object} skippedFeaturesHash Ids of features to skip. */ -ol.render.webgl.LineStringReplay.prototype.drawReplay_ = - function(gl, context, skippedFeaturesHash) { +ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { if (!goog.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { @@ -1160,8 +1183,7 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = /** * @inheritDoc */ -ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = - function(fillStyle, strokeStyle) { +ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { if (strokeStyle) { var strokeStyleColor = strokeStyle.getColor(); this.strokeColor_ = !goog.isNull(strokeStyleColor) ? @@ -1174,7 +1196,6 @@ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = }; - /** * @constructor * @extends {ol.render.VectorContext} @@ -1192,6 +1213,11 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { */ this.fillColor_ = undefined; + /** + * @private + * @type {ol.Color|undefined} + */ + this.strokeColor_ = undefined; /** * @private @@ -1262,11 +1288,10 @@ goog.inherits(ol.render.webgl.PolygonReplay, ol.render.VectorContext); /** * Draw one polygon. - * @param {Array.>} coordinates + * @param {Array.>} coordinates Coordinates. * @private */ -ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = - function(coordinates) { +ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(coordinates) { // Triangulate the polgon var triangulation = ol.ext.earcut(coordinates, true); var i, ii; @@ -1295,8 +1320,7 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = /** * @inheritDoc */ -ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = - function(geometry, feature) { +ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(geometry, feature) { if (goog.isNull(this.fillColor_)) { return; } @@ -1313,8 +1337,7 @@ ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = /** * @inheritDoc */ -ol.render.webgl.PolygonReplay.prototype.drawPolygon = - function(polygonGeometry, feature) { +ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { if (goog.isNull(this.fillColor_)) { return; } @@ -1358,8 +1381,7 @@ ol.render.webgl.PolygonReplay.prototype.finish = function(context) { * @param {ol.webgl.Context} context WebGL context. * @return {function()} Delete resources function. */ -ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = - function(context) { +ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(context) { // We only delete our stuff here. The shaders and the program may // be used by other PolygonReplay instances (for other layers). And // they will be deleted when disposing of the ol.webgl.Context @@ -1483,8 +1505,7 @@ ol.render.webgl.PolygonReplay.prototype.replay = function(context, * @param {ol.webgl.Context} context Context. * @param {Object} skippedFeaturesHash Ids of features to skip. */ -ol.render.webgl.PolygonReplay.prototype.drawReplay_ = - function(gl, context, skippedFeaturesHash) { +ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { var elementType = context.hasOESElementIndexUint ? goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; // var elementSize = context.hasOESElementIndexUint ? 4 : 2; @@ -1502,8 +1523,7 @@ ol.render.webgl.PolygonReplay.prototype.drawReplay_ = * @inheritDoc */ -ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = - function(fillStyle, strokeStyle) { +ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { // TODO implement if (fillStyle) { var fillStyleColor = fillStyle.getColor(); @@ -1514,11 +1534,19 @@ ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = } else { this.fillColor_ = undefined; } + if (strokeStyle) { + var strokeStyleColor = strokeStyle.getColor(); + this.strokeColor_ = !goog.isNull(strokeStyleColor) ? + ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) : ol.render.webgl.defaultStrokeStyle; + } else { + this.strokeColor_ = undefined; + } this.lineStringReplay_.setFillStrokeStyle(fillStyle, strokeStyle); }; - /** * @constructor * @extends {ol.render.ReplayGroup} @@ -1550,7 +1578,7 @@ ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { /** * ImageReplay only is supported at this point. - * @type {Object.} + * @type {Object.} * @private */ this.replays_ = {}; @@ -1776,13 +1804,13 @@ ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( * @const * @private * @type {Object.} */ ol.render.webgl.BATCH_CONSTRUCTORS_ = { 'Image': ol.render.webgl.ImageReplay, - 'LineString': ol.render.webgl.LineStringReplay, - 'Polygon': ol.render.webgl.PolygonReplay + 'LineString': ol.render.webgl.LineStringReplay//, + //'Polygon': ol.render.webgl.PolygonReplay }; diff --git a/src/ol/render/webgl/webgl.js b/src/ol/render/webgl/webgl.js index 4fb7bfe513..9f34dca2eb 100644 --- a/src/ol/render/webgl/webgl.js +++ b/src/ol/render/webgl/webgl.js @@ -11,3 +11,9 @@ ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0]; * @type {ol.Color} */ ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0]; + +/** + * @const + * @type {number} + */ +ol.render.webgl.defaultLineWidth = 1; diff --git a/src/ol/render/webgl/webglpolygondefaultshader.js b/src/ol/render/webgl/webglpolygondefaultshader.js index 0aa28b8f3d..972a6f5edd 100644 --- a/src/ol/render/webgl/webglpolygondefaultshader.js +++ b/src/ol/render/webgl/webglpolygondefaultshader.js @@ -7,7 +7,6 @@ goog.provide('ol.render.webgl.polygonreplay.shader.DefaultVertex'); goog.require('ol.webgl.shader'); - /** * @constructor * @extends {ol.webgl.shader.Fragment} @@ -43,7 +42,6 @@ ol.render.webgl.polygonreplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE; - /** * @constructor * @extends {ol.webgl.shader.Vertex} @@ -79,7 +77,6 @@ ol.render.webgl.polygonreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE; - /** * @constructor * @param {WebGLRenderingContext} gl GL. From 6e958514eeb045cfab65fadefbc4e2fccde29977 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 1 Jun 2016 20:12:56 +0200 Subject: [PATCH 10/88] Taking line width into account --- src/ol/render/webgl/imagereplay/index.js | 63 ++++++++++++++++-------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 9cc5260ac0..8fa5f079ec 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -155,7 +155,7 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.indicesBuffer_ = null; - /** +/** * @private * @type {ol.render.webgl.imagereplay.defaultshader.Locations} */ @@ -975,7 +975,7 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { */ this.strokeColor_ = undefined; - /** +/** * @private * @type {ol.render.webgl.polygonreplay.shader.Default.Locations} */ @@ -992,6 +992,25 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { * @private */ this.verticesBuffer_ = null; + + /** + * @private + * @type {{strokeColor: (Array.|undefined), + * lineCap: (string|undefined), + * lineDash: Array., + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined)}|null} + */ + this.state_ = { + strokeColor: undefined, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined + }; + }; goog.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); @@ -1009,18 +1028,18 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(coordinat var point1 = coordinates[i]; this.vertices_.push(point1[0]); this.vertices_.push(point1[1]); - this.vertices_.push(this.strokeColor_[0]); - this.vertices_.push(this.strokeColor_[1]); - this.vertices_.push(this.strokeColor_[2]); - this.vertices_.push(this.strokeColor_[3]); + this.vertices_.push(this.state_.strokeColor[0]); + this.vertices_.push(this.state_.strokeColor[1]); + this.vertices_.push(this.state_.strokeColor[2]); + this.vertices_.push(this.state_.strokeColor[3]); var point2 = coordinates[i + 1]; this.vertices_.push(point2[0]); this.vertices_.push(point2[1]); - this.vertices_.push(this.strokeColor_[0]); - this.vertices_.push(this.strokeColor_[1]); - this.vertices_.push(this.strokeColor_[2]); - this.vertices_.push(this.strokeColor_[3]); + this.vertices_.push(this.state_.strokeColor[0]); + this.vertices_.push(this.state_.strokeColor[1]); + this.vertices_.push(this.state_.strokeColor[2]); + this.vertices_.push(this.state_.strokeColor[3]); } }; @@ -1173,7 +1192,9 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s } else { var numItems = this.vertices_.length / 6; // FIXME: not compatible with batching, hardcoding some arbitrary value - gl.lineWidth(3); + if (this.state_.lineWidth) { + gl.lineWidth(this.state_.lineWidth); + } gl.drawArrays(goog.webgl.LINES, 0, numItems); gl.lineWidth(1); } @@ -1184,15 +1205,17 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s * @inheritDoc */ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - if (strokeStyle) { - var strokeStyleColor = strokeStyle.getColor(); - this.strokeColor_ = !goog.isNull(strokeStyleColor) ? - ol.color.asArray(strokeStyleColor).map(function(c, i) { - return i != 3 ? c / 255 : c; - }) : ol.render.webgl.defaultStrokeStyle; - } else { - this.strokeColor_ = undefined; - } + goog.asserts.assert(this.state_, 'this.state_ should not be null'); + goog.asserts.assert(!fillStyle, 'fillStyle should be null'); + goog.asserts.assert(strokeStyle, 'strokeStyle should not be null'); + var strokeStyleColor = strokeStyle.getColor(); + this.state_.strokeColor = !goog.isNull(strokeStyleColor) ? + ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) : ol.render.webgl.defaultStrokeStyle; + var strokeStyleWidth = strokeStyle.getWidth(); + this.state_.lineWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.webgl.defaultLineWidth; }; From caeb8e4820a9228015fdc9dd670d1e33398b3a58 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Thu, 2 Jun 2016 20:44:22 +0200 Subject: [PATCH 11/88] Eliminating jitter by restructuring --- src/ol/render/webgl/imagereplay/index.js | 400 +++++++++--------- src/ol/render/webgl/webglpolygondefault.glsl | 13 +- .../render/webgl/webglpolygondefaultshader.js | 26 +- 3 files changed, 223 insertions(+), 216 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 8fa5f079ec..d8eb21f6eb 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -68,7 +68,50 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { * @type {!goog.vec.Mat4.Number} * @private */ - this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity(); + this.projectionMatrix_ = ol.transform.create(); + + /** + * @type {!goog.vec.Mat4.Number} + * @private + */ + this.offsetRotateMatrix_ = ol.transform.create(); + + /** + * @type {!goog.vec.Mat4.Number} + * @private + */ + this.offsetScaleMatrix_ = ol.transform.create(); + + /** + * @type {Array.} + * @private + */ + this.tmpMat4_ = ol.vec.Mat4.create(); + + /** + * @type {Array.} + * @private + */ + this.indices_ = []; + + /** + * @type {ol.webgl.Buffer} + * @private + */ + this.indicesBuffer_ = null; + + /** + * @type {Array.} + * @private + */ + this.vertices_ = []; + + /** + * @type {ol.webgl.Buffer} + * @private + */ + this.verticesBuffer_ = null; + }; goog.inherits(ol.render.webgl.Replay, ol.render.VectorContext); @@ -76,7 +119,89 @@ ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = goog.abstractMetho ol.render.webgl.Replay.prototype.finish = goog.abstractMethod; -ol.render.webgl.Replay.prototype.replay = goog.abstractMethod; +ol.render.webgl.Replay.prototype.setUpProgram_ = goog.abstractMethod; + +ol.render.webgl.Replay.prototype.drawReplay_ = goog.abstractMethod; + +ol.render.webgl.Replay.prototype.drawHitDetectionReplay_ = goog.abstractMethod; + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + var gl = context.getGL(); + + // bind the vertices buffer + goog.asserts.assert(this.verticesBuffer_, + 'verticesBuffer must not be null'); + context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + + // bind the indices buffer + goog.asserts.assert(!goog.isNull(this.indicesBuffer_), + 'indicesBuffer must not be null'); + context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + + var locations = this.setUpProgram_(gl, context); + + // set the "uniform" values + var projectionMatrix = ol.transform.reset(this.projectionMatrix_); + ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1])); + ol.transform.rotate(projectionMatrix, -rotation); + ol.transform.translate(projectionMatrix, -(center[0] - this.origin_[0]), -(center[1] - this.origin_[1])); + + var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_); + ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]); + + var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_); + if (rotation !== 0) { + ol.transform.rotate(offsetRotateMatrix, -rotation); + } + + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix)); + gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix)); + gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix)); + gl.uniform1f(locations.u_opacity, opacity); + + // draw! + var result; + if (featureCallback === undefined) { + this.drawReplay_(gl, context, skippedFeaturesHash); + } else { + // draw feature by feature for the hit-detection + result = this.drawHitDetectionReplay_(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } + + // disable the vertex attrib arrays + for (var i in locations) { + if (i.startsWith('a_')) { + gl.disableVertexAttribArray(locations[i]); + } + } + + + return result; +}; /** * @constructor @@ -144,18 +269,6 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { this.imageWidth_ = undefined; /** - * @type {Array.} - * @private - */ - this.indices_ = []; - - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.indicesBuffer_ = null; - -/** * @private * @type {ol.render.webgl.imagereplay.defaultshader.Locations} */ @@ -167,18 +280,6 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.opacity_ = undefined; - /** - * @type {ol.Transform} - * @private - */ - this.offsetRotateMatrix_ = ol.transform.create(); - - /** - * @type {ol.Transform} - * @private - */ - this.offsetScaleMatrix_ = ol.transform.create(); - /** * @type {number|undefined} * @private @@ -191,18 +292,6 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.originY_ = undefined; - /** - * @type {ol.Transform} - * @private - */ - this.projectionMatrix_ = ol.transform.create(); - - /** - * @type {Array.} - * @private - */ - this.tmpMat4_ = ol.vec.Mat4.create(); - /** * @private * @type {boolean|undefined} @@ -233,18 +322,6 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.hitDetectionTextures_ = []; - /** - * @type {Array.} - * @private - */ - this.vertices_ = []; - - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.verticesBuffer_ = null; - /** * Start index per feature (the index). * @type {Array.} @@ -536,38 +613,12 @@ ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, image /** + * @private + * @param {WebGLRenderingContext} gl gl. * @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 + * @return {ol.render.webgl.imagereplay.shader.Default.Locations} Locations. */ -ol.render.webgl.ImageReplay.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent) { - var gl = context.getGL(); - - // bind the vertices buffer - ol.DEBUG && console.assert(this.verticesBuffer_, - 'verticesBuffer must not be null'); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); - - // bind the indices buffer - ol.DEBUG && console.assert(this.indicesBuffer_, - 'indecesBuffer must not be null'); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - +ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context) { // get the program var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment; var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex; @@ -607,60 +658,19 @@ ol.render.webgl.ImageReplay.prototype.replay = function(context, gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, false, 32, 28); - // 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, - this.textures_, this.groupIndices_); - } else { - // draw feature by feature for the hit-detection - result = this.drawHitDetectionReplay_(gl, context, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent); - } - - // disable the vertex attrib arrays - 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); - - return result; + return locations; }; - /** * @private * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. * @param {Object.} skippedFeaturesHash Ids of features * to skip. - * @param {Array.} textures Textures. - * @param {Array.} groupIndices Texture group indices. */ -ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, textures, groupIndices) { +ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { + var textures = this.textures_; + var groupIndices = this.groupIndices_; ol.DEBUG && console.assert(textures.length === groupIndices.length, 'number of textures and groupIndeces match'); var elementType = context.hasOESElementIndexUint ? @@ -969,30 +979,12 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { goog.base(this, tolerance, maxExtent); - /** - * @private - * @type {ol.Color|undefined} - */ - this.strokeColor_ = undefined; - /** * @private * @type {ol.render.webgl.polygonreplay.shader.Default.Locations} */ this.defaultLocations_ = null; - /** - * @type {Array.} - * @private - */ - this.vertices_ = []; - - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.verticesBuffer_ = null; - /** * @private * @type {{strokeColor: (Array.|undefined), @@ -1022,24 +1014,25 @@ goog.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); */ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(coordinates) { var i, ii; + var numVertices = this.vertices_.length; // Shift the indices to take into account previously handled lines for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { var point1 = coordinates[i]; - this.vertices_.push(point1[0]); - this.vertices_.push(point1[1]); - this.vertices_.push(this.state_.strokeColor[0]); - this.vertices_.push(this.state_.strokeColor[1]); - this.vertices_.push(this.state_.strokeColor[2]); - this.vertices_.push(this.state_.strokeColor[3]); + this.vertices_[numVertices++] = point1[0] - this.origin_[0]; + this.vertices_[numVertices++] = point1[1] - this.origin_[1]; + this.vertices_[numVertices++] = this.state_.strokeColor[0]; + this.vertices_[numVertices++] = this.state_.strokeColor[1]; + this.vertices_[numVertices++] = this.state_.strokeColor[2]; + this.vertices_[numVertices++] = this.state_.strokeColor[3]; var point2 = coordinates[i + 1]; - this.vertices_.push(point2[0]); - this.vertices_.push(point2[1]); - this.vertices_.push(this.state_.strokeColor[0]); - this.vertices_.push(this.state_.strokeColor[1]); - this.vertices_.push(this.state_.strokeColor[2]); - this.vertices_.push(this.state_.strokeColor[3]); + this.vertices_[numVertices++] = point2[0] - this.origin_[0]; + this.vertices_[numVertices++] = point2[1] - this.origin_[1]; + this.vertices_[numVertices++] = this.state_.strokeColor[0]; + this.vertices_[numVertices++] = this.state_.strokeColor[1]; + this.vertices_[numVertices++] = this.state_.strokeColor[2]; + this.vertices_[numVertices++] = this.state_.strokeColor[3]; } }; @@ -1071,6 +1064,10 @@ ol.render.webgl.LineStringReplay.prototype.finish = function(context) { // create, bind, and populate the vertices buffer this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + + // create, bind, and populate the indices buffer + this.indicesBuffer_ = new ol.webgl.Buffer(this.indices_); + context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); }; @@ -1086,43 +1083,21 @@ ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function goog.asserts.assert(!goog.isNull(this.verticesBuffer_), 'verticesBuffer must not be null'); var verticesBuffer = this.verticesBuffer_; + var indicesBuffer = this.indicesBuffer_; return function() { context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); }; }; /** + * @private + * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @param {number} opacity Global opacity. - * @param {number} brightness Global brightness. - * @param {number} contrast Global contrast. - * @param {number} hue Global hue. - * @param {number} saturation Global saturation. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. - * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T + * @return {ol.render.webgl.polygonreplay.shader.Default.Locations} Locations. */ -ol.render.webgl.LineStringReplay.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent) { - var gl = context.getGL(); - - // bind the vertices buffer - goog.asserts.assert(!goog.isNull(this.verticesBuffer_), - 'verticesBuffer must not be null'); - context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); - +ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context) { // get the program var fragmentShader, vertexShader; fragmentShader = @@ -1152,31 +1127,7 @@ ol.render.webgl.LineStringReplay.prototype.replay = function(context, gl.vertexAttribPointer(locations.a_color, 4, goog.webgl.FLOAT, false, 24, 8); - // set the "uniform" values - // TODO: use RTE to avoid jitter - var projectionMatrix = this.projectionMatrix_; - ol.vec.Mat4.makeTransform2D(projectionMatrix, - 0.0, 0.0, - pixelRatio * 2 / (resolution * size[0]), - pixelRatio * 2 / (resolution * size[1]), - -rotation, - -center[0], -center[1]); - - gl.uniformMatrix4fv(locations.u_projectionMatrix, false, projectionMatrix); - - // draw! - var result; - if (!goog.isDef(featureCallback)) { - this.drawReplay_(gl, context, skippedFeaturesHash); - } else { - // TODO: draw feature by feature for the hit-detection - } - - // disable the vertex attrib arrays - gl.disableVertexAttribArray(locations.a_position); - gl.disableVertexAttribArray(locations.a_color); - - return result; + return locations; }; @@ -1200,6 +1151,33 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s } }; +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.LineStringReplay.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); + //} + return 0; +}; + /** * @inheritDoc @@ -1369,7 +1347,7 @@ ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, this.startIndicesFeature_.push(feature); this.drawCoordinates_(coordinates); - if (!goog.isNull(this.lineStringReplay_.strokeColor_)) { + if (!goog.isNull(this.lineStringReplay_.state_.strokeColor)) { var linearRings = polygonGeometry.getLinearRings(); var i, ii; for (i = 0, ii = linearRings.length; i < ii; i++) { @@ -1437,7 +1415,7 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(co * @param {number} hue Global hue. * @param {number} saturation Global saturation. * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @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. @@ -1515,7 +1493,7 @@ ol.render.webgl.PolygonReplay.prototype.replay = function(context, this.lineStringReplay_.replay(context, center, resolution, rotation, size, pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + opacity, skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); // FIXME get result return result; diff --git a/src/ol/render/webgl/webglpolygondefault.glsl b/src/ol/render/webgl/webglpolygondefault.glsl index 59712c4228..4d81a78a5c 100644 --- a/src/ol/render/webgl/webglpolygondefault.glsl +++ b/src/ol/render/webgl/webglpolygondefault.glsl @@ -11,15 +11,26 @@ attribute vec2 a_position; attribute vec4 a_color; uniform mat4 u_projectionMatrix; +uniform mat4 u_offsetScaleMatrix; +uniform mat4 u_offsetRotateMatrix; void main(void) { v_color = a_color; - gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.); + mat4 offsetMatrix = u_offsetScaleMatrix; + vec4 offsets = offsetMatrix * vec4(0., 0., 0., 0.); + gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets; } //! FRAGMENT +uniform float u_opacity; + void main(void) { gl_FragColor = v_color; + float alpha = v_color.a * u_opacity; + if (alpha == 0.0) { + discard; + } + gl_FragColor.a = alpha; } diff --git a/src/ol/render/webgl/webglpolygondefaultshader.js b/src/ol/render/webgl/webglpolygondefaultshader.js index 972a6f5edd..ef25c0815d 100644 --- a/src/ol/render/webgl/webglpolygondefaultshader.js +++ b/src/ol/render/webgl/webglpolygondefaultshader.js @@ -23,14 +23,14 @@ goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultFragment); * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec4 v_color;\n\n\n\nvoid main(void) {\n gl_FragColor = v_color;\n}\n'; +ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec4 v_color;\n\n\n\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = v_color;\n float alpha = v_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; /** * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec4 a;void main(void){gl_FragColor=a;}'; +ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec4 a;uniform float g;void main(void){gl_FragColor=a;float alpha=a.a*g;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; /** @@ -58,14 +58,14 @@ goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec4 v_color;\n\n\nattribute vec2 a_position;\nattribute vec4 a_color;\n\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n v_color = a_color;\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n}\n\n\n'; +ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec4 v_color;\n\n\nattribute vec2 a_position;\nattribute vec4 a_color;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n v_color = a_color;\n mat4 offsetMatrix = u_offsetScaleMatrix;\n vec4 offsets = offsetMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec4 a;attribute vec2 b;attribute vec4 c;uniform mat4 d;void main(void){a=c;gl_Position=d*vec4(b,0.,1.);}'; +ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec4 a;attribute vec2 b;attribute vec4 c;uniform mat4 d;uniform mat4 e;uniform mat4 f;void main(void){a=c;mat4 offsetMatrix=e;vec4 offsets=offsetMatrix*vec4(0.,0.,0.,0.);gl_Position=d*vec4(b,0.,1.)+offsets;}'; /** @@ -85,6 +85,24 @@ ol.render.webgl.polygonreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? */ ol.render.webgl.polygonreplay.shader.Default.Locations = function(gl, program) { + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'f'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, goog.DEBUG ? 'u_opacity' : 'g'); + /** * @type {WebGLUniformLocation} */ From c7edd21cc3498d3b3100dc2510c0edb937a631f5 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 6 Jun 2016 17:39:12 +0200 Subject: [PATCH 12/88] Extend linestring renderer with GLSL and indexed geometries. --- src/ol/render/webgl/imagereplay/index.js | 78 ++++++----- src/ol/render/webgl/webgl.js | 27 ++++ .../render/webgl/webgllinestringdefault.glsl | 34 +++++ .../webgl/webgllinestringdefaultshader.js | 123 ++++++++++++++++++ 4 files changed, 229 insertions(+), 33 deletions(-) create mode 100644 src/ol/render/webgl/webgllinestringdefault.glsl create mode 100644 src/ol/render/webgl/webgllinestringdefaultshader.js diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index d8eb21f6eb..1d6f5e69af 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -15,6 +15,10 @@ goog.require('ol.render.replay'); goog.require('ol.render.webgl.imagereplay.defaultshader'); goog.require('ol.transform'); goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.linestringreplay.shader.Default'); +goog.require('ol.render.webgl.linestringreplay.shader.Default.Locations'); +goog.require('ol.render.webgl.linestringreplay.shader.DefaultFragment'); +goog.require('ol.render.webgl.linestringreplay.shader.DefaultVertex'); goog.require('ol.render.webgl.polygonreplay.shader.Default'); goog.require('ol.render.webgl.polygonreplay.shader.Default.Locations'); goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); @@ -1009,30 +1013,33 @@ goog.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); /** * Draw one line. - * @param {Array.} coordinates Coordinates. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. * @private */ -ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(coordinates) { +ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { var i, ii; var numVertices = this.vertices_.length; + var numIndices = this.indices_.length; + var n = this.indices_.length > 0 ? this.indices_[numIndices - 1] + 1 : 0; // Shift the indices to take into account previously handled lines - for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { - var point1 = coordinates[i]; - this.vertices_[numVertices++] = point1[0] - this.origin_[0]; - this.vertices_[numVertices++] = point1[1] - this.origin_[1]; - this.vertices_[numVertices++] = this.state_.strokeColor[0]; - this.vertices_[numVertices++] = this.state_.strokeColor[1]; - this.vertices_[numVertices++] = this.state_.strokeColor[2]; - this.vertices_[numVertices++] = this.state_.strokeColor[3]; + for (i = offset, ii = end - stride; i < ii; i += stride) { - var point2 = coordinates[i + 1]; - this.vertices_[numVertices++] = point2[0] - this.origin_[0]; - this.vertices_[numVertices++] = point2[1] - this.origin_[1]; - this.vertices_[numVertices++] = this.state_.strokeColor[0]; - this.vertices_[numVertices++] = this.state_.strokeColor[1]; - this.vertices_[numVertices++] = this.state_.strokeColor[2]; - this.vertices_[numVertices++] = this.state_.strokeColor[3]; + if (i == offset) { + this.vertices_[numVertices++] = flatCoordinates[i] - this.origin_[0]; + this.vertices_[numVertices++] = flatCoordinates[i + 1] - this.origin_[1]; + + i += stride; + } + + this.vertices_[numVertices++] = flatCoordinates[i] - this.origin_[0]; + this.vertices_[numVertices++] = flatCoordinates[i + 1] - this.origin_[1]; + + this.indices_[numIndices++] = n++; + this.indices_[numIndices++] = n; } }; @@ -1040,19 +1047,25 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(coordinat /** * @inheritDoc */ -ol.render.webgl.LineStringReplay.prototype.drawLineString = function(geometry, feature) { - this.drawCoordinates_(geometry.getCoordinates()); +ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { + var flatCoordinates = lineStringGeometry.getFlatCoordinates(); + var stride = lineStringGeometry.getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); }; /** * @inheritDoc */ -ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(geometry, feature) { - var coordinatess = geometry.getCoordinates(); +ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { + var lineStringGeometries = multiLineStringGeometry.getLineStrings(); var i, ii; - for (i = 0, ii = coordinatess.length; i < ii; ++i) { - this.drawCoordinates_(coordinatess[i]); + for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { + var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); + var stride = lineStringGeometries[i].getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); } }; @@ -1095,21 +1108,21 @@ ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function * @private * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. - * @return {ol.render.webgl.polygonreplay.shader.Default.Locations} Locations. + * @return {ol.render.webgl.linestringreplay.shader.Default.Locations} Locations. */ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context) { // get the program var fragmentShader, vertexShader; fragmentShader = - ol.render.webgl.polygonreplay.shader.DefaultFragment.getInstance(); + ol.render.webgl.linestringreplay.shader.DefaultFragment.getInstance(); vertexShader = - ol.render.webgl.polygonreplay.shader.DefaultVertex.getInstance(); + ol.render.webgl.linestringreplay.shader.DefaultVertex.getInstance(); var program = context.getProgram(fragmentShader, vertexShader); // get the locations var locations; if (goog.isNull(this.defaultLocations_)) { - locations = new ol.render.webgl.polygonreplay.shader.Default + locations = new ol.render.webgl.linestringreplay.shader.Default .Locations(gl, program); this.defaultLocations_ = locations; } else { @@ -1121,11 +1134,10 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context) // enable the vertex attrib arrays gl.enableVertexAttribArray(locations.a_position); gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, - false, 24, 0); + false, 8, 0); - gl.enableVertexAttribArray(locations.a_color); - gl.vertexAttribPointer(locations.a_color, 4, goog.webgl.FLOAT, - false, 24, 8); + // enable renderer specific uniforms + gl.uniform4fv(locations.u_color, this.state_.strokeColor); return locations; }; @@ -1141,12 +1153,12 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s if (!goog.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { - var numItems = this.vertices_.length / 6; + var numItems = this.indices_.length; // FIXME: not compatible with batching, hardcoding some arbitrary value if (this.state_.lineWidth) { gl.lineWidth(this.state_.lineWidth); } - gl.drawArrays(goog.webgl.LINES, 0, numItems); + gl.drawElements(goog.webgl.LINES, numItems, goog.webgl.UNSIGNED_SHORT, 0); gl.lineWidth(1); } }; diff --git a/src/ol/render/webgl/webgl.js b/src/ol/render/webgl/webgl.js index 9f34dca2eb..262a7639d1 100644 --- a/src/ol/render/webgl/webgl.js +++ b/src/ol/render/webgl/webgl.js @@ -6,6 +6,33 @@ goog.provide('ol.render.webgl'); */ ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0]; +/** + * @const + * @type {string} + */ +ol.render.webgl.defaultLineCap = 'round'; + + +/** + * @const + * @type {Array.} + */ +ol.render.webgl.defaultLineDash = []; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.defaultLineJoin = 'round'; + + +/** + * @const + * @type {number} + */ +ol.render.webgl.defaultMiterLimit = 10; + /** * @const * @type {ol.Color} diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/webgllinestringdefault.glsl new file mode 100644 index 0000000000..8b81af911e --- /dev/null +++ b/src/ol/render/webgl/webgllinestringdefault.glsl @@ -0,0 +1,34 @@ +//! NAMESPACE=ol.render.webgl.linestringreplay.shader.Default +//! CLASS=ol.render.webgl.linestringreplay.shader.Default + + +//! COMMON + + +//! VERTEX +attribute vec2 a_position; + +uniform mat4 u_projectionMatrix; +uniform mat4 u_offsetScaleMatrix; +uniform mat4 u_offsetRotateMatrix; + +void main(void) { + mat4 offsetMatrix = u_offsetScaleMatrix; + vec4 offsets = offsetMatrix * vec4(0., 0., 0., 0.); + gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets; +} + + +//! FRAGMENT + +uniform float u_opacity; +uniform vec4 u_color; + +void main(void) { + gl_FragColor = u_color; + float alpha = u_color.a * u_opacity; + if (alpha == 0.0) { + discard; + } + gl_FragColor.a = alpha; +} diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/webgllinestringdefaultshader.js new file mode 100644 index 0000000000..d886ff52fb --- /dev/null +++ b/src/ol/render/webgl/webgllinestringdefaultshader.js @@ -0,0 +1,123 @@ +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.linestringreplay.shader.Default'); +goog.provide('ol.render.webgl.linestringreplay.shader.Default.Locations'); +goog.provide('ol.render.webgl.linestringreplay.shader.DefaultFragment'); +goog.provide('ol.render.webgl.linestringreplay.shader.DefaultVertex'); + +goog.require('ol.webgl.shader'); + + +/** + * @constructor + * @extends {ol.webgl.shader.Fragment} + * @struct + */ +ol.render.webgl.linestringreplay.shader.DefaultFragment = function() { + goog.base(this, ol.render.webgl.linestringreplay.shader.DefaultFragment.SOURCE); +}; +goog.inherits(ol.render.webgl.linestringreplay.shader.DefaultFragment, ol.webgl.shader.Fragment); +goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultFragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\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.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform float e;uniform vec4 f;void main(void){gl_FragColor=f;float alpha=f.a*e;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? + ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE; + + +/** + * @constructor + * @extends {ol.webgl.shader.Vertex} + * @struct + */ +ol.render.webgl.linestringreplay.shader.DefaultVertex = function() { + goog.base(this, ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE); +}; +goog.inherits(ol.render.webgl.linestringreplay.shader.DefaultVertex, ol.webgl.shader.Vertex); +goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n vec4 offsets = offsetMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){mat4 offsetMatrix=c;vec4 offsets=offsetMatrix*vec4(0.,0.,0.,0.);gl_Position=b*vec4(a,0.,1.)+offsets;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? + ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE; + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.linestringreplay.shader.Default.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, goog.DEBUG ? 'u_color' : 'f'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'd'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'c'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, goog.DEBUG ? 'u_opacity' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_projectionMatrix' : 'b'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, goog.DEBUG ? 'a_position' : 'a'); +}; From 0972988bdbc561d622de2cde6a1d7944c23fb5ad Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 13 Jun 2016 10:48:49 +0200 Subject: [PATCH 13/88] Properly triangulate linestrings --- src/ol/render/webgl/imagereplay/index.js | 331 +++++++++++++++--- .../render/webgl/webgllinestringdefault.glsl | 79 ++++- .../webgl/webgllinestringdefaultshader.js | 64 +++- 3 files changed, 403 insertions(+), 71 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 1d6f5e69af..9306999eac 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -104,6 +104,20 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.indicesBuffer_ = null; + /** + * Start index per feature (the index). + * @type {Array.} + * @private + */ + this.startIndices_ = []; + + /** + * Start index per feature (the feature). + * @type {Array.} + * @private + */ + this.startIndicesFeature_ = []; + /** * @type {Array.} * @private @@ -119,6 +133,23 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { }; goog.inherits(ol.render.webgl.Replay, ol.render.VectorContext); + +/** + * @enum {number} + */ +ol.render.webgl.LineStringInstruction = { + BEGIN_LINE: 0, + END_LINE: 1, + SQUARE_CAP: 2, + BEVEL_FIRST: 3, + BEVEL_SECOND: 4, + MITER_BOTTOM: 5, + MITER_TOP: 6, + ROUND_JOIN: 7, + ROUND_CAP: 8, + ROUND_BOTH: 9 +}; + ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = goog.abstractMethod; ol.render.webgl.Replay.prototype.finish = goog.abstractMethod; @@ -162,7 +193,7 @@ ol.render.webgl.Replay.prototype.replay = function(context, 'indicesBuffer must not be null'); context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - var locations = this.setUpProgram_(gl, context); + var locations = this.setUpProgram_(gl, context, size); // set the "uniform" values var projectionMatrix = ol.transform.reset(this.projectionMatrix_); @@ -207,6 +238,21 @@ ol.render.webgl.Replay.prototype.replay = function(context, return result; }; +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {number} start Start index. + * @param {number} end End index. + * @param {number} elementType Element type. + * @param {number} elementSize Element Size. + */ +ol.render.webgl.Replay.prototype.drawElements_ = function( + gl, start, end, elementType, elementSize) { + var numItems = end - start; + var offsetInBytes = start * elementSize; + gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); +}; + /** * @constructor * @extends {ol.render.webgl.Replay} @@ -326,20 +372,6 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.hitDetectionTextures_ = []; - /** - * Start index per feature (the index). - * @type {Array.} - * @private - */ - this.startIndices_ = []; - - /** - * Start index per feature (the feature). - * @type {Array.} - * @private - */ - this.startIndicesFeature_ = []; - /** * @type {number|undefined} * @private @@ -620,9 +652,10 @@ ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, image * @private * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. + * @param {ol.Size} size Size. * @return {ol.render.webgl.imagereplay.shader.Default.Locations} Locations. */ -ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context) { +ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context, size) { // get the program var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment; var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex; @@ -768,22 +801,6 @@ ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, skipped }; -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {number} start Start index. - * @param {number} end End index. - * @param {number} elementType Element type. - * @param {number} elementSize Element Size. - */ -ol.render.webgl.ImageReplay.prototype.drawElements_ = function( - gl, start, end, elementType, elementSize) { - var numItems = end - start; - var offsetInBytes = start * elementSize; - gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes); -}; - - /** * @private * @param {WebGLRenderingContext} gl gl. @@ -1020,29 +1037,182 @@ goog.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); * @private */ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + var i, ii; var numVertices = this.vertices_.length; var numIndices = this.indices_.length; - var n = this.indices_.length > 0 ? this.indices_[numIndices - 1] + 1 : 0; + var lineJoin = this.state_.lineJoin; + var verticesPerPoint = lineJoin === 'bevel' ? 3 : 4; + //var lineCap = this.state_.lineCap; + var closed = this.isClosed_(flatCoordinates, offset, end, stride); + var lastIndex; + var lastSign = 1; + //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. + //We rotate those points, thus every point is RTE corrected only once. + var p0, p1, p2, tempP; - // Shift the indices to take into account previously handled lines - for (i = offset, ii = end - stride; i < ii; i += stride) { + for (i = offset, ii = end; i < ii; i += stride) { - if (i == offset) { - this.vertices_[numVertices++] = flatCoordinates[i] - this.origin_[0]; - this.vertices_[numVertices++] = flatCoordinates[i + 1] - this.origin_[1]; + var n = numVertices / 8; - i += stride; + tempP = tempP || p1; + p1 = p2 || [flatCoordinates[i] - this.origin_[0], flatCoordinates[i + 1] - this.origin_[1]]; + //First vertex. + if (i === offset) { + p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; + if (closed) { + //A closed line! Complete the circle. + tempP = [flatCoordinates[end - stride] - this.origin_[0], flatCoordinates[end - stride + 1] - this.origin_[1]]; + } else { + //Add the first two vertices. + p0 = undefined; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = p1[0]; + this.vertices_[numVertices++] = p1[1]; + this.vertices_[numVertices++] = p2[0]; + this.vertices_[numVertices++] = p2[1]; + this.vertices_[numVertices++] = 1; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE; + + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = p1[0]; + this.vertices_[numVertices++] = p1[1]; + this.vertices_[numVertices++] = p2[0]; + this.vertices_[numVertices++] = p2[1]; + this.vertices_[numVertices++] = -1; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE; + + lastIndex = n + 1; + + continue; + } + } else if (i === end - stride) { + //Last vertex. + if (closed) { + p2 = [flatCoordinates[offset] - this.origin_[0], flatCoordinates[offset + 1] - this.origin_[1]]; + } else { + p2 = undefined; + p0 = tempP || p0; + + this.vertices_[numVertices++] = p0[0]; + this.vertices_[numVertices++] = p0[1]; + this.vertices_[numVertices++] = p1[0]; + this.vertices_[numVertices++] = p1[1]; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = lastSign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.END_LINE; + + this.vertices_[numVertices++] = p0[0]; + this.vertices_[numVertices++] = p0[1]; + this.vertices_[numVertices++] = p1[0]; + this.vertices_[numVertices++] = p1[1]; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = -lastSign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.END_LINE; + + 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; + + break; + } + } else { + p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; } + //Sort out duplicate points. + if (ol.array.equals(p1, p2)) continue; + p0 = tempP || p0; + tempP = undefined; + //The sign of the area determines the line segment's orientation. + var sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) + ? 1 : -1; - this.vertices_[numVertices++] = flatCoordinates[i] - this.origin_[0]; - this.vertices_[numVertices++] = flatCoordinates[i + 1] - this.origin_[1]; + 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++] = sign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEVEL_FIRST; + + 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++] = sign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEVEL_SECOND; + + 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++] = -sign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.MITER_BOTTOM; - this.indices_[numIndices++] = n++; 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 (verticesPerPoint === 4) { + 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++] = sign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.MITER_TOP; + + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 3; + this.indices_[numIndices++] = n; + } } }; +/** + * Check if the linestring is a boundary. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} The linestring is a boundary. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.isClosed_ = function(flatCoordinates, offset, end, stride) { + var lastCoord = end - stride; + if (flatCoordinates[offset] === flatCoordinates[lastCoord] && + flatCoordinates[offset + 1] === flatCoordinates[lastCoord + 1]) { + return true; + } + return false; +}; + /** * @inheritDoc @@ -1050,8 +1220,12 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { var flatCoordinates = lineStringGeometry.getFlatCoordinates(); var stride = lineStringGeometry.getStride(); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); + if (flatCoordinates.length > stride) { + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } }; @@ -1059,13 +1233,12 @@ ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringG * @inheritDoc */ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); var lineStringGeometries = multiLineStringGeometry.getLineStrings(); var i, ii; for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { - var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); - var stride = lineStringGeometries[i].getStride(); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); + this.drawLineString(lineStringGeometries[i], feature); } }; @@ -1074,6 +1247,8 @@ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiL * @param {ol.webgl.Context} context Context. **/ ol.render.webgl.LineStringReplay.prototype.finish = function(context) { + this.startIndices_.push(this.indices_.length); + // create, bind, and populate the vertices buffer this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); @@ -1081,6 +1256,9 @@ ol.render.webgl.LineStringReplay.prototype.finish = function(context) { // create, bind, and populate the indices buffer this.indicesBuffer_ = new ol.webgl.Buffer(this.indices_); context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + + this.vertices_ = null; + this.indices_ = null; }; @@ -1108,9 +1286,10 @@ ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function * @private * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. + * @param {ol.Size} size Size. * @return {ol.render.webgl.linestringreplay.shader.Default.Locations} Locations. */ -ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context) { +ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, size) { // get the program var fragmentShader, vertexShader; fragmentShader = @@ -1132,12 +1311,37 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context) context.useProgram(program); // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_lastPos); + gl.vertexAttribPointer(locations.a_lastPos, 2, goog.webgl.FLOAT, + false, 32, 0); + gl.enableVertexAttribArray(locations.a_position); gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, - false, 8, 0); + false, 32, 8); + + gl.enableVertexAttribArray(locations.a_nextPos); + gl.vertexAttribPointer(locations.a_nextPos, 2, goog.webgl.FLOAT, + false, 32, 16); + + gl.enableVertexAttribArray(locations.a_direction); + gl.vertexAttribPointer(locations.a_direction, 1, goog.webgl.FLOAT, + false, 32, 24); + + gl.enableVertexAttribArray(locations.a_instruction); + gl.vertexAttribPointer(locations.a_instruction, 1, goog.webgl.FLOAT, + false, 32, 28); + + var round = this.state_.lineJoin === 'round' && this.state_.lineCap === 'round' ? + ol.render.webgl.LineStringInstruction.ROUND_BOTH : this.state_.lineJoin === 'round' ? + ol.render.webgl.LineStringInstruction.ROUND_JOIN : this.state_.lineCap === 'round' ? + ol.render.webgl.LineStringInstruction.ROUND_CAP : 0; // enable renderer specific uniforms gl.uniform4fv(locations.u_color, this.state_.strokeColor); + gl.uniform1f(locations.u_lineWidth, this.state_.lineWidth); + gl.uniform1f(locations.u_miterLimit, this.state_.miterLimit); + gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_round, round); return locations; }; @@ -1150,16 +1354,19 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context) * @param {Object} skippedFeaturesHash Ids of features to skip. */ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { + var elementType = context.hasOESElementIndexUint ? + goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; + var elementSize = context.hasOESElementIndexUint ? 4 : 2; + if (!goog.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { - var numItems = this.indices_.length; - // FIXME: not compatible with batching, hardcoding some arbitrary value - if (this.state_.lineWidth) { - gl.lineWidth(this.state_.lineWidth); + var i, ii; + for (i = 0, ii = this.startIndices_.length - 1; i < ii; ++i) { + var start = this.startIndices_[i]; + var end = this.startIndices_[i + 1]; + this.drawElements_(gl, start, end, elementType, elementSize); } - gl.drawElements(goog.webgl.LINES, numItems, goog.webgl.UNSIGNED_SHORT, 0); - gl.lineWidth(1); } }; @@ -1203,9 +1410,21 @@ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillSty ol.color.asArray(strokeStyleColor).map(function(c, i) { return i != 3 ? c / 255 : c; }) : ol.render.webgl.defaultStrokeStyle; + var strokeStyleLineCap = strokeStyle.getLineCap(); + this.state_.lineCap = strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.webgl.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.webgl.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + this.state_.lineJoin = strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.webgl.defaultLineJoin; var strokeStyleWidth = strokeStyle.getWidth(); this.state_.lineWidth = strokeStyleWidth !== undefined ? strokeStyleWidth : ol.render.webgl.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + this.state_.miterLimit = strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.webgl.defaultMiterLimit; }; diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/webgllinestringdefault.glsl index 8b81af911e..67c2c5937a 100644 --- a/src/ol/render/webgl/webgllinestringdefault.glsl +++ b/src/ol/render/webgl/webgllinestringdefault.glsl @@ -3,19 +3,83 @@ //! COMMON +varying float v_round; +varying vec4 v_roundVertex; +varying float v_halfWidth; //! VERTEX +attribute vec2 a_lastPos; attribute vec2 a_position; +attribute vec2 a_nextPos; +attribute float a_direction; +attribute float a_instruction; uniform mat4 u_projectionMatrix; uniform mat4 u_offsetScaleMatrix; -uniform mat4 u_offsetRotateMatrix; +uniform float u_lineWidth; +uniform float u_miterLimit; +uniform float u_round; void main(void) { - mat4 offsetMatrix = u_offsetScaleMatrix; - vec4 offsets = offsetMatrix * vec4(0., 0., 0., 0.); - gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets; + v_halfWidth = u_lineWidth / 2.0; + vec2 offset; + v_round = 0.0; + vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.); + if (a_instruction == 0. || a_instruction == 4.) { + vec2 dirVect = a_nextPos - a_position; + vec2 normal = normalize(vec2(-dirVect.y, dirVect.x)); + offset = v_halfWidth * normal * a_direction; + if (a_instruction == 4. && (u_round == 7. || u_round == 9.)) { + v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); + } + } else if (a_instruction == 1. || a_instruction == 3.) { + vec2 dirVect = a_lastPos - a_position; + vec2 normal = normalize(vec2(dirVect.y, -dirVect.x)); + offset = v_halfWidth * normal * a_direction; + if (a_instruction == 3. && (u_round == 7. || u_round == 9.)) { + v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); + } + } else if (a_instruction == 5. || a_instruction == 6.) { + vec2 dirVect = a_nextPos - a_position; + vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x)); + vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos)); + vec2 normal = vec2(tangent.y, -tangent.x); + float miterLength = v_halfWidth / dot(normal, tmpNormal); + if (a_instruction == 6.) { + if (u_round == 7. || u_round == 9.) { + offset = normal * a_direction * miterLength; + v_round = 1.0; + v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); + } else if (miterLength > u_miterLimit) { + offset = tmpNormal * a_direction * v_halfWidth; + } else { + offset = normal * a_direction * miterLength; + } + } else if (a_instruction == 5.) { + offset = normal * a_direction * miterLength; + vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.); + vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset; + vec4 secondProjPos = projPos + defaultOffset; + vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset; + float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy); + float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy); + float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy); + //TODO: Write a more accurate method for identifying sharp angles. + if (miterSegLength > min(firstSegLength, secondSegLength)) { + if (firstSegLength < secondSegLength) { + dirVect = a_lastPos - a_position; + tmpNormal = normalize(vec2(dirVect.y, -dirVect.x)); + projPos = firstProjPos - defaultOffset; + } else { + projPos = thirdProjPos - defaultOffset; + } + offset = tmpNormal * a_direction * v_halfWidth; + } + } + } + vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.); + gl_Position = projPos + offsets; } @@ -23,8 +87,15 @@ void main(void) { uniform float u_opacity; uniform vec4 u_color; +uniform vec2 u_size; void main(void) { + if (v_round > 0.0) { + vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x, (v_roundVertex.y + 1.0) / 2.0 * u_size.y); + if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth) { + discard; + } + } gl_FragColor = u_color; float alpha = u_color.a * u_opacity; if (alpha == 0.0) { diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/webgllinestringdefaultshader.js index d886ff52fb..110a3b1600 100644 --- a/src/ol/render/webgl/webgllinestringdefaultshader.js +++ b/src/ol/render/webgl/webgllinestringdefaultshader.js @@ -23,14 +23,14 @@ goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultFragment) * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\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'; +ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x, (v_roundVertex.y + 1.0) / 2.0 * u_size.y);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform float e;uniform vec4 f;void main(void){gl_FragColor=f;float alpha=f.a*e;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; +ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec4 b;varying float c;uniform float n;uniform vec4 o;uniform vec2 p;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*p.x,(b.y+1.0)/2.0*p.y);if(length(windowCoords-gl_FragCoord.xy)>c){discard;}} gl_FragColor=o;float alpha=o.a*n;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; /** @@ -58,14 +58,14 @@ goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n vec4 offsets = offsetMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\nattribute float a_instruction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\nuniform float u_round;\n\nvoid main(void) {\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (a_instruction == 0. || a_instruction == 4.) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * a_direction;\n if (a_instruction == 4. && (u_round == 7. || u_round == 9.)) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 1. || a_instruction == 3.) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * a_direction;\n if (a_instruction == 3. && (u_round == 7. || u_round == 9.)) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 5. || a_instruction == 6.) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(tangent.y, -tangent.x);\n float miterLength = v_halfWidth / dot(normal, tmpNormal);\n if (a_instruction == 6.) {\n if (u_round == 7. || u_round == 9.) {\n offset = normal * a_direction * miterLength;\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * a_direction * v_halfWidth;\n } else {\n offset = normal * a_direction * miterLength;\n }\n } else if (a_instruction == 5.) {\n offset = normal * a_direction * miterLength;\n vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset;\n vec4 secondProjPos = projPos + defaultOffset;\n vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset;\n float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy);\n float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy);\n float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy);\n //TODO: Write a more accurate method for identifying sharp angles.\n if (miterSegLength > min(firstSegLength, secondSegLength)) {\n if (firstSegLength < secondSegLength) {\n dirVect = a_lastPos - a_position;\n tmpNormal = normalize(vec2(dirVect.y, -dirVect.x));\n projPos = firstProjPos - defaultOffset;\n } else {\n projPos = thirdProjPos - defaultOffset;\n }\n offset = tmpNormal * a_direction * v_halfWidth;\n }\n }\n }\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){mat4 offsetMatrix=c;vec4 offsets=offsetMatrix*vec4(0.,0.,0.,0.);gl_Position=b*vec4(a,0.,1.)+offsets;}'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec4 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;attribute float h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;uniform float m;void main(void){c=k/2.0;vec2 offset;a=0.0;vec4 projPos=i*vec4(e,0.,1.);if(h==0.||h==4.){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*g;if(h==4.&&(m==7.||m==9.)){b=projPos+j*vec4(0.,0.,0.,0.);}} else if(h==1.||h==3.){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*g;if(h==3.&&(m==7.||m==9.)){b=projPos+j*vec4(0.,0.,0.,0.);}} else if(h==5.||h==6.){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(tangent.y,-tangent.x);float miterLength=c/dot(normal,tmpNormal);if(h==6.){if(m==7.||m==9.){offset=normal*g*miterLength;a=1.0;b=projPos+j*vec4(0.,0.,0.,0.);}else if(miterLength>l){offset=tmpNormal*g*c;}else{offset=normal*g*miterLength;}} else if(h==5.){offset=normal*g*miterLength;vec4 defaultOffset=j*vec4(0.,0.,0.,0.);vec4 firstProjPos=i*vec4(d,0.,1.)+defaultOffset;vec4 secondProjPos=projPos+defaultOffset;vec4 thirdProjPos=i*vec4(f,0.,1.)+defaultOffset;float firstSegLength=distance(secondProjPos.xy,firstProjPos.xy);float secondSegLength=distance(thirdProjPos.xy,secondProjPos.xy);float miterSegLength=distance(secondProjPos.xy,vec4(projPos+j*vec4(offset,0.,0.)).xy);if(miterSegLength>min(firstSegLength,secondSegLength)){if(firstSegLength Date: Mon, 13 Jun 2016 11:24:51 +0200 Subject: [PATCH 14/88] Change goog.base to call, and goog.inherits to ol.inherits --- src/ol/render/webgl/imagereplay/index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 9306999eac..4a67a57b09 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -36,7 +36,7 @@ goog.require('ol.webgl.Context'); * @protected * @struct */ -ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { +ol.render.webgl.Replay = function(tolerance, maxExtent) { ol.render.VectorContext.call(this); /** @@ -262,7 +262,7 @@ ol.render.webgl.Replay.prototype.drawElements_ = function( * @struct */ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { - goog.base(this, tolerance, maxExtent); + ol.render.webgl.Replay.call(this, tolerance, maxExtent); /** * @type {number|undefined} @@ -378,7 +378,7 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.width_ = undefined; }; -ol.inherits(ol.render.webgl.ImageReplay, ol.render.VectorContext); +ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay); /** @@ -998,7 +998,7 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { * @struct */ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { - goog.base(this, tolerance, maxExtent); + ol.render.webgl.Replay.call(this, tolerance, maxExtent); /** * @private @@ -1025,7 +1025,7 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { }; }; -goog.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); +ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); /** @@ -1437,7 +1437,7 @@ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillSty * @struct */ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { - goog.base(this); + ol.render.webgl.Replay.call(this, tolerance, maxExtent); /** * @private @@ -1515,7 +1515,7 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { */ this.startIndicesFeature_ = []; }; -goog.inherits(ol.render.webgl.PolygonReplay, ol.render.VectorContext); +ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); /** From fb71860a036965a796f06430b71a905ef7424b4d Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 14 Jun 2016 14:25:21 +0200 Subject: [PATCH 15/88] Adding linestring cap support --- src/ol/render/webgl/imagereplay/index.js | 78 ++++++++++++++++--- .../render/webgl/webgllinestringdefault.glsl | 30 ++++++- .../webgl/webgllinestringdefaultshader.js | 12 +-- .../render/webgl/webglpolygondefaultshader.js | 8 +- 4 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 4a67a57b09..50aea34db9 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -140,14 +140,15 @@ goog.inherits(ol.render.webgl.Replay, ol.render.VectorContext); ol.render.webgl.LineStringInstruction = { BEGIN_LINE: 0, END_LINE: 1, - SQUARE_CAP: 2, + BEGIN_LINE_CAP: 2, BEVEL_FIRST: 3, BEVEL_SECOND: 4, MITER_BOTTOM: 5, MITER_TOP: 6, ROUND_JOIN: 7, ROUND_CAP: 8, - ROUND_BOTH: 9 + ROUND_BOTH: 9, + END_LINE_CAP : 10 }; ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = goog.abstractMethod; @@ -1041,9 +1042,8 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord var i, ii; var numVertices = this.vertices_.length; var numIndices = this.indices_.length; - var lineJoin = this.state_.lineJoin; - var verticesPerPoint = lineJoin === 'bevel' ? 3 : 4; - //var lineCap = this.state_.lineCap; + var lineJoin = this.state_.lineJoin === 'bevel' ? false : true; + var lineCap = this.state_.lineCap === 'butt' ? false : true; var closed = this.isClosed_(flatCoordinates, offset, end, stride); var lastIndex; var lastSign = 1; @@ -1064,15 +1064,46 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord //A closed line! Complete the circle. tempP = [flatCoordinates[end - stride] - this.origin_[0], flatCoordinates[end - stride + 1] - this.origin_[1]]; } else { - //Add the first two vertices. + //Add the first two/four vertices. p0 = undefined; + + if (lineCap) { + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = p1[0]; + this.vertices_[numVertices++] = p1[1]; + this.vertices_[numVertices++] = p2[0]; + this.vertices_[numVertices++] = p2[1]; + this.vertices_[numVertices++] = lastSign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP; + + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = p1[0]; + this.vertices_[numVertices++] = p1[1]; + this.vertices_[numVertices++] = p2[0]; + this.vertices_[numVertices++] = p2[1]; + this.vertices_[numVertices++] = -lastSign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP; + + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = n + 1; + + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 3; + this.indices_[numIndices++] = n + 2; + + n = n + 2; + } + this.vertices_[numVertices++] = 0; this.vertices_[numVertices++] = 0; this.vertices_[numVertices++] = p1[0]; this.vertices_[numVertices++] = p1[1]; this.vertices_[numVertices++] = p2[0]; this.vertices_[numVertices++] = p2[1]; - this.vertices_[numVertices++] = 1; + this.vertices_[numVertices++] = lastSign; this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE; this.vertices_[numVertices++] = 0; @@ -1081,7 +1112,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord this.vertices_[numVertices++] = p1[1]; this.vertices_[numVertices++] = p2[0]; this.vertices_[numVertices++] = p2[1]; - this.vertices_[numVertices++] = -1; + this.vertices_[numVertices++] = -lastSign; this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE; lastIndex = n + 1; @@ -1122,6 +1153,35 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord this.indices_[numIndices++] = n + 1; this.indices_[numIndices++] = n; + if (lineCap) { + this.vertices_[numVertices++] = p0[0]; + this.vertices_[numVertices++] = p0[1]; + this.vertices_[numVertices++] = p1[0]; + this.vertices_[numVertices++] = p1[1]; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = lastSign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.END_LINE_CAP; + + this.vertices_[numVertices++] = p0[0]; + this.vertices_[numVertices++] = p0[1]; + this.vertices_[numVertices++] = p1[0]; + this.vertices_[numVertices++] = p1[1]; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = 0; + this.vertices_[numVertices++] = -lastSign; + this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.END_LINE_CAP; + + 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 { @@ -1178,7 +1238,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord lastSign = sign; //Add miter - if (verticesPerPoint === 4) { + if (lineJoin) { this.vertices_[numVertices++] = p0[0]; this.vertices_[numVertices++] = p0[1]; this.vertices_[numVertices++] = p1[0]; diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/webgllinestringdefault.glsl index 67c2c5937a..d48c34eb54 100644 --- a/src/ol/render/webgl/webgllinestringdefault.glsl +++ b/src/ol/render/webgl/webgllinestringdefault.glsl @@ -30,14 +30,16 @@ void main(void) { vec2 dirVect = a_nextPos - a_position; vec2 normal = normalize(vec2(-dirVect.y, dirVect.x)); offset = v_halfWidth * normal * a_direction; - if (a_instruction == 4. && (u_round == 7. || u_round == 9.)) { + if ((a_instruction == 4. && (u_round == 7. || u_round == 9.)) || + (a_instruction == 0. && (u_round == 8. || u_round == 9.))) { v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } } else if (a_instruction == 1. || a_instruction == 3.) { vec2 dirVect = a_lastPos - a_position; vec2 normal = normalize(vec2(dirVect.y, -dirVect.x)); offset = v_halfWidth * normal * a_direction; - if (a_instruction == 3. && (u_round == 7. || u_round == 9.)) { + if ((a_instruction == 3. && (u_round == 7. || u_round == 9.)) || + (a_instruction == 1. && (u_round == 8. || u_round == 9.))) { v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } } else if (a_instruction == 5. || a_instruction == 6.) { @@ -77,6 +79,30 @@ void main(void) { offset = tmpNormal * a_direction * v_halfWidth; } } + } else if (a_instruction == 2.) { + vec2 dirVect = a_position - a_nextPos; + vec2 firstNormal = normalize(dirVect); + vec2 secondNormal = vec2(firstNormal.y * a_direction, -firstNormal.x * a_direction); + vec2 hypotenuse = normalize(firstNormal - secondNormal); + vec2 normal = vec2(hypotenuse.y * a_direction, -hypotenuse.x * a_direction); + float length = sqrt(v_halfWidth * v_halfWidth * 2.0); + offset = normal * length; + if (u_round == 8. || u_round == 9.) { + v_round = 1.0; + v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); + } + } else if (a_instruction == 10.) { + vec2 dirVect = a_position - a_lastPos; + vec2 firstNormal = normalize(dirVect); + vec2 secondNormal = vec2(-firstNormal.y * a_direction, firstNormal.x * a_direction); + vec2 hypotenuse = normalize(firstNormal - secondNormal); + vec2 normal = vec2(-hypotenuse.y * a_direction, hypotenuse.x * a_direction); + float length = sqrt(v_halfWidth * v_halfWidth * 2.0); + offset = normal * length; + if (u_round == 8. || u_round == 9.) { + v_round = 1.0; + v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); + } } vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.); gl_Position = projPos + offsets; diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/webgllinestringdefaultshader.js index 110a3b1600..5050d095f5 100644 --- a/src/ol/render/webgl/webgllinestringdefaultshader.js +++ b/src/ol/render/webgl/webgllinestringdefaultshader.js @@ -13,9 +13,9 @@ goog.require('ol.webgl.shader'); * @struct */ ol.render.webgl.linestringreplay.shader.DefaultFragment = function() { - goog.base(this, ol.render.webgl.linestringreplay.shader.DefaultFragment.SOURCE); + ol.webgl.shader.Fragment.call(this, ol.render.webgl.linestringreplay.shader.DefaultFragment.SOURCE); }; -goog.inherits(ol.render.webgl.linestringreplay.shader.DefaultFragment, ol.webgl.shader.Fragment); +ol.inherits(ol.render.webgl.linestringreplay.shader.DefaultFragment, ol.webgl.shader.Fragment); goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultFragment); @@ -48,9 +48,9 @@ ol.render.webgl.linestringreplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? * @struct */ ol.render.webgl.linestringreplay.shader.DefaultVertex = function() { - goog.base(this, ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE); + ol.webgl.shader.Vertex.call(this, ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE); }; -goog.inherits(ol.render.webgl.linestringreplay.shader.DefaultVertex, ol.webgl.shader.Vertex); +ol.inherits(ol.render.webgl.linestringreplay.shader.DefaultVertex, ol.webgl.shader.Vertex); goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); @@ -58,14 +58,14 @@ goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\nattribute float a_instruction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\nuniform float u_round;\n\nvoid main(void) {\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (a_instruction == 0. || a_instruction == 4.) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * a_direction;\n if (a_instruction == 4. && (u_round == 7. || u_round == 9.)) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 1. || a_instruction == 3.) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * a_direction;\n if (a_instruction == 3. && (u_round == 7. || u_round == 9.)) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 5. || a_instruction == 6.) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(tangent.y, -tangent.x);\n float miterLength = v_halfWidth / dot(normal, tmpNormal);\n if (a_instruction == 6.) {\n if (u_round == 7. || u_round == 9.) {\n offset = normal * a_direction * miterLength;\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * a_direction * v_halfWidth;\n } else {\n offset = normal * a_direction * miterLength;\n }\n } else if (a_instruction == 5.) {\n offset = normal * a_direction * miterLength;\n vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset;\n vec4 secondProjPos = projPos + defaultOffset;\n vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset;\n float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy);\n float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy);\n float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy);\n //TODO: Write a more accurate method for identifying sharp angles.\n if (miterSegLength > min(firstSegLength, secondSegLength)) {\n if (firstSegLength < secondSegLength) {\n dirVect = a_lastPos - a_position;\n tmpNormal = normalize(vec2(dirVect.y, -dirVect.x));\n projPos = firstProjPos - defaultOffset;\n } else {\n projPos = thirdProjPos - defaultOffset;\n }\n offset = tmpNormal * a_direction * v_halfWidth;\n }\n }\n }\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n}\n\n\n'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\nattribute float a_instruction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\nuniform float u_round;\n\nvoid main(void) {\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (a_instruction == 0. || a_instruction == 4.) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * a_direction;\n if ((a_instruction == 4. && (u_round == 7. || u_round == 9.)) ||\n (a_instruction == 0. && (u_round == 8. || u_round == 9.))) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 1. || a_instruction == 3.) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * a_direction;\n if ((a_instruction == 3. && (u_round == 7. || u_round == 9.)) ||\n (a_instruction == 1. && (u_round == 8. || u_round == 9.))) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 5. || a_instruction == 6.) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(tangent.y, -tangent.x);\n float miterLength = v_halfWidth / dot(normal, tmpNormal);\n if (a_instruction == 6.) {\n if (u_round == 7. || u_round == 9.) {\n offset = normal * a_direction * miterLength;\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * a_direction * v_halfWidth;\n } else {\n offset = normal * a_direction * miterLength;\n }\n } else if (a_instruction == 5.) {\n offset = normal * a_direction * miterLength;\n vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset;\n vec4 secondProjPos = projPos + defaultOffset;\n vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset;\n float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy);\n float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy);\n float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy);\n //TODO: Write a more accurate method for identifying sharp angles.\n if (miterSegLength > min(firstSegLength, secondSegLength)) {\n if (firstSegLength < secondSegLength) {\n dirVect = a_lastPos - a_position;\n tmpNormal = normalize(vec2(dirVect.y, -dirVect.x));\n projPos = firstProjPos - defaultOffset;\n } else {\n projPos = thirdProjPos - defaultOffset;\n }\n offset = tmpNormal * a_direction * v_halfWidth;\n }\n }\n } else if (a_instruction == 2.) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * a_direction, -firstNormal.x * a_direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n vec2 normal = vec2(hypotenuse.y * a_direction, -hypotenuse.x * a_direction);\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (u_round == 8. || u_round == 9.) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 10.) {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * a_direction, firstNormal.x * a_direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n vec2 normal = vec2(-hypotenuse.y * a_direction, hypotenuse.x * a_direction);\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (u_round == 8. || u_round == 9.) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n }\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec4 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;attribute float h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;uniform float m;void main(void){c=k/2.0;vec2 offset;a=0.0;vec4 projPos=i*vec4(e,0.,1.);if(h==0.||h==4.){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*g;if(h==4.&&(m==7.||m==9.)){b=projPos+j*vec4(0.,0.,0.,0.);}} else if(h==1.||h==3.){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*g;if(h==3.&&(m==7.||m==9.)){b=projPos+j*vec4(0.,0.,0.,0.);}} else if(h==5.||h==6.){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(tangent.y,-tangent.x);float miterLength=c/dot(normal,tmpNormal);if(h==6.){if(m==7.||m==9.){offset=normal*g*miterLength;a=1.0;b=projPos+j*vec4(0.,0.,0.,0.);}else if(miterLength>l){offset=tmpNormal*g*c;}else{offset=normal*g*miterLength;}} else if(h==5.){offset=normal*g*miterLength;vec4 defaultOffset=j*vec4(0.,0.,0.,0.);vec4 firstProjPos=i*vec4(d,0.,1.)+defaultOffset;vec4 secondProjPos=projPos+defaultOffset;vec4 thirdProjPos=i*vec4(f,0.,1.)+defaultOffset;float firstSegLength=distance(secondProjPos.xy,firstProjPos.xy);float secondSegLength=distance(thirdProjPos.xy,secondProjPos.xy);float miterSegLength=distance(secondProjPos.xy,vec4(projPos+j*vec4(offset,0.,0.)).xy);if(miterSegLength>min(firstSegLength,secondSegLength)){if(firstSegLength Date: Tue, 14 Jun 2016 20:34:11 +0200 Subject: [PATCH 16/88] Handle case of one segment lines with identical coordinates + quality improvements --- src/ol/render/webgl/imagereplay/index.js | 71 ++++++++++++++---------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 50aea34db9..4321c30fc8 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -21,8 +21,8 @@ goog.require('ol.render.webgl.linestringreplay.shader.DefaultFragment'); goog.require('ol.render.webgl.linestringreplay.shader.DefaultVertex'); goog.require('ol.render.webgl.polygonreplay.shader.Default'); goog.require('ol.render.webgl.polygonreplay.shader.Default.Locations'); -goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); -goog.require('ol.render.webgl.polygonreplay.shader.DefaultVertex'); +//goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); +//goog.require('ol.render.webgl.polygonreplay.shader.DefaultVertex'); goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); @@ -705,10 +705,11 @@ ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context, size * @param {ol.webgl.Context} context Context. * @param {Object.} skippedFeaturesHash Ids of features * to skip. + * @param {boolean} hitDetection Hit detection mode. */ -ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { - var textures = this.textures_; - var groupIndices = this.groupIndices_; +ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { + var textures = hitDetection ? this.hitDetectionTextures_ : this.textures_; + var groupIndices = hitDetection ? this.hitDetectionGroupIndices_ : this.groupIndices_; ol.DEBUG && console.assert(textures.length === groupIndices.length, 'number of textures and groupIndeces match'); var elementType = context.hasOESElementIndexUint ? @@ -841,8 +842,7 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = function(gl, con */ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayAll_ = function(gl, context, skippedFeaturesHash, featureCallback) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawReplay_(gl, context, skippedFeaturesHash, - this.hitDetectionTextures_, this.hitDetectionGroupIndices_); + this.drawReplay_(gl, context, skippedFeaturesHash, true); var result = featureCallback(null); if (result) { @@ -1003,13 +1003,13 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { /** * @private - * @type {ol.render.webgl.polygonreplay.shader.Default.Locations} + * @type {ol.render.webgl.linestringreplay.shader.Default.Locations} */ this.defaultLocations_ = null; /** * @private - * @type {{strokeColor: (Array.|undefined), + * @type {{strokeColor: (Array.|null), * lineCap: (string|undefined), * lineDash: Array., * lineJoin: (string|undefined), @@ -1017,7 +1017,7 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { * miterLimit: (number|undefined)}|null} */ this.state_ = { - strokeColor: undefined, + strokeColor: null, lineCap: undefined, lineDash: null, lineJoin: undefined, @@ -1045,7 +1045,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord var lineJoin = this.state_.lineJoin === 'bevel' ? false : true; var lineCap = this.state_.lineCap === 'butt' ? false : true; var closed = this.isClosed_(flatCoordinates, offset, end, stride); - var lastIndex; + var lastIndex = numIndices; var lastSign = 1; //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. //We rotate those points, thus every point is RTE corrected only once. @@ -1060,6 +1060,9 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord //First vertex. if (i === offset) { p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; + if (flatCoordinates.length === stride * 2 && ol.array.equals(p1, p2)) { + break; + } if (closed) { //A closed line! Complete the circle. tempP = [flatCoordinates[end - stride] - this.origin_[0], flatCoordinates[end - stride + 1] - this.origin_[1]]; @@ -1298,7 +1301,14 @@ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiL var lineStringGeometries = multiLineStringGeometry.getLineStrings(); var i, ii; for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { - this.drawLineString(lineStringGeometries[i], feature); + var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); + var stride = lineStringGeometries[i].getStride(); + if (flatCoordinates.length > stride) { + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } } }; @@ -1396,10 +1406,14 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, ol.render.webgl.LineStringInstruction.ROUND_JOIN : this.state_.lineCap === 'round' ? ol.render.webgl.LineStringInstruction.ROUND_CAP : 0; - // enable renderer specific uniforms + // Enable renderer specific uniforms. If clauses needed, as otherwise the compiler complains. gl.uniform4fv(locations.u_color, this.state_.strokeColor); - gl.uniform1f(locations.u_lineWidth, this.state_.lineWidth); - gl.uniform1f(locations.u_miterLimit, this.state_.miterLimit); + if (this.state_.lineWidth) { + gl.uniform1f(locations.u_lineWidth, this.state_.lineWidth); + } + if (this.state_.miterLimit) { + gl.uniform1f(locations.u_miterLimit, this.state_.miterLimit); + } gl.uniform2fv(locations.u_size, size); gl.uniform1f(locations.u_round, round); @@ -1412,8 +1426,9 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {boolean} hitDetection Hit detection mode. */ -ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { +ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { var elementType = context.hasOESElementIndexUint ? goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; var elementSize = context.hasOESElementIndexUint ? 4 : 2; @@ -1421,12 +1436,8 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s if (!goog.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { - var i, ii; - for (i = 0, ii = this.startIndices_.length - 1; i < ii; ++i) { - var start = this.startIndices_[i]; - var end = this.startIndices_[i + 1]; - this.drawElements_(gl, start, end, elementType, elementSize); - } + var end = this.startIndices_[this.startIndices_.length - 1]; + this.drawElements_(gl, 0, end, elementType, elementSize); } }; @@ -1466,10 +1477,9 @@ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillSty goog.asserts.assert(!fillStyle, 'fillStyle should be null'); goog.asserts.assert(strokeStyle, 'strokeStyle should not be null'); var strokeStyleColor = strokeStyle.getColor(); - this.state_.strokeColor = !goog.isNull(strokeStyleColor) ? - ol.color.asArray(strokeStyleColor).map(function(c, i) { - return i != 3 ? c / 255 : c; - }) : ol.render.webgl.defaultStrokeStyle; + this.state_.strokeColor = ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultStrokeStyle; var strokeStyleLineCap = strokeStyle.getLineCap(); this.state_.lineCap = strokeStyleLineCap !== undefined ? strokeStyleLineCap : ol.render.webgl.defaultLineCap; @@ -1490,7 +1500,7 @@ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillSty /** * @constructor - * @extends {ol.render.VectorContext} + * @extends {ol.render.webgl.Replay} * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. * @protected @@ -1642,7 +1652,8 @@ ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, var linearRings = polygonGeometry.getLinearRings(); var i, ii; for (i = 0, ii = linearRings.length; i < ii; i++) { - this.lineStringReplay_.drawCoordinates_(linearRings[i].getCoordinates()); + //FIXME: Substitute zeros with appropriate values when implementing. + this.lineStringReplay_.drawCoordinates_(linearRings[i].getFlatCoordinates(), 0, 0, 0); } } }; @@ -1713,7 +1724,7 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(co * @return {T|undefined} Callback result. * @template T */ -ol.render.webgl.PolygonReplay.prototype.replay = function(context, +/*ol.render.webgl.PolygonReplay.prototype.replay = function(context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) { @@ -1788,7 +1799,7 @@ ol.render.webgl.PolygonReplay.prototype.replay = function(context, featureCallback, oneByOne, opt_hitExtent); // FIXME get result return result; -}; +};*/ /** From 0f8e1a7e405b132fe8643b1efbe4d185da34dcc4 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 15 Jun 2016 11:42:08 +0200 Subject: [PATCH 17/88] Refactoring ol.render.webgl.LineStringReplay --- src/ol/render/webgl/imagereplay/index.js | 197 +++++++----------- .../render/webgl/webgllinestringdefault.glsl | 68 +++--- .../webgl/webgllinestringdefaultshader.js | 32 +-- 3 files changed, 111 insertions(+), 186 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 4321c30fc8..c8d590348e 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -138,17 +138,15 @@ goog.inherits(ol.render.webgl.Replay, ol.render.VectorContext); * @enum {number} */ ol.render.webgl.LineStringInstruction = { - BEGIN_LINE: 0, - END_LINE: 1, - BEGIN_LINE_CAP: 2, - BEVEL_FIRST: 3, - BEVEL_SECOND: 4, - MITER_BOTTOM: 5, - MITER_TOP: 6, - ROUND_JOIN: 7, - ROUND_CAP: 8, - ROUND_BOTH: 9, - END_LINE_CAP : 10 + 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 }; ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = goog.abstractMethod; @@ -1030,7 +1028,7 @@ ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); /** - * Draw one line. + * Draw one segment. * @param {Array.} flatCoordinates Flat coordinates. * @param {number} offset Offset. * @param {number} end End. @@ -1042,8 +1040,13 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord var i, ii; var numVertices = this.vertices_.length; var numIndices = this.indices_.length; - var lineJoin = this.state_.lineJoin === 'bevel' ? false : true; - var lineCap = this.state_.lineCap === 'butt' ? false : true; + //To save a vertex, the direction of a point is a product of the sign (1 or -1), a prime from + //ol.render.webgl.LineStringInstruction, and a rounding factor (1 or 2). If the product is even, + //we round it. If it is odd, we don't. + var lineJoin = this.state_.lineJoin === 'bevel' ? 0 : + this.state_.lineJoin === 'miter' ? 1 : 2; + var lineCap = this.state_.lineCap === 'butt' ? 0 : + this.state_.lineCap === 'square' ? 1 : 2; var closed = this.isClosed_(flatCoordinates, offset, end, stride); var lastIndex = numIndices; var lastSign = 1; @@ -1053,7 +1056,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord for (i = offset, ii = end; i < ii; i += stride) { - var n = numVertices / 8; + var n = numVertices / 7; tempP = tempP || p1; p1 = p2 || [flatCoordinates[i] - this.origin_[0], flatCoordinates[i + 1] - this.origin_[1]]; @@ -1068,26 +1071,13 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord tempP = [flatCoordinates[end - stride] - this.origin_[0], flatCoordinates[end - stride + 1] - this.origin_[1]]; } else { //Add the first two/four vertices. - p0 = undefined; if (lineCap) { - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = p2[0]; - this.vertices_[numVertices++] = p2[1]; - this.vertices_[numVertices++] = lastSign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP; + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = p2[0]; - this.vertices_[numVertices++] = p2[1]; - this.vertices_[numVertices++] = -lastSign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP; + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); this.indices_[numIndices++] = n + 2; this.indices_[numIndices++] = n; @@ -1100,23 +1090,11 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord n = n + 2; } - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = p2[0]; - this.vertices_[numVertices++] = p2[1]; - this.vertices_[numVertices++] = lastSign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE; + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = p2[0]; - this.vertices_[numVertices++] = p2[1]; - this.vertices_[numVertices++] = -lastSign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEGIN_LINE; + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); lastIndex = n + 1; @@ -1128,25 +1106,15 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord p2 = [flatCoordinates[offset] - this.origin_[0], flatCoordinates[offset + 1] - this.origin_[1]]; } else { p2 = undefined; - p0 = tempP || p0; + //Note, that the third case will never happen, we just have to assure the compiler, + //p0 is always an array of nums. + p0 = tempP || p0 || [0, 0]; - this.vertices_[numVertices++] = p0[0]; - this.vertices_[numVertices++] = p0[1]; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = lastSign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.END_LINE; + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); - this.vertices_[numVertices++] = p0[0]; - this.vertices_[numVertices++] = p0[1]; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = -lastSign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.END_LINE; + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); this.indices_[numIndices++] = n; this.indices_[numIndices++] = lastIndex - 1; @@ -1157,23 +1125,11 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord this.indices_[numIndices++] = n; if (lineCap) { - this.vertices_[numVertices++] = p0[0]; - this.vertices_[numVertices++] = p0[1]; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = lastSign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.END_LINE_CAP; + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); - this.vertices_[numVertices++] = p0[0]; - this.vertices_[numVertices++] = p0[1]; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = 0; - this.vertices_[numVertices++] = -lastSign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.END_LINE_CAP; + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); this.indices_[numIndices++] = n + 2; this.indices_[numIndices++] = n; @@ -1198,32 +1154,14 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord var sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) ? 1 : -1; - 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++] = sign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEVEL_FIRST; + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.BEVEL_FIRST * (lineJoin || 1), 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++] = sign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.BEVEL_SECOND; + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.BEVEL_SECOND * (lineJoin || 1), 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++] = -sign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.MITER_BOTTOM; + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.LineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); this.indices_[numIndices++] = n; this.indices_[numIndices++] = lastIndex - 1; @@ -1242,14 +1180,8 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord //Add miter if (lineJoin) { - 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++] = sign; - this.vertices_[numVertices++] = ol.render.webgl.LineStringInstruction.MITER_TOP; + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.MITER_TOP * lineJoin, numVertices); this.indices_[numIndices++] = n + 1; this.indices_[numIndices++] = n + 3; @@ -1258,6 +1190,27 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord } }; +/** + * @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 is a boundary. * @param {Array.} flatCoordinates Flat coordinates. @@ -1383,28 +1336,19 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, // enable the vertex attrib arrays gl.enableVertexAttribArray(locations.a_lastPos); gl.vertexAttribPointer(locations.a_lastPos, 2, goog.webgl.FLOAT, - false, 32, 0); + false, 28, 0); gl.enableVertexAttribArray(locations.a_position); gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, - false, 32, 8); + false, 28, 8); gl.enableVertexAttribArray(locations.a_nextPos); gl.vertexAttribPointer(locations.a_nextPos, 2, goog.webgl.FLOAT, - false, 32, 16); + false, 28, 16); gl.enableVertexAttribArray(locations.a_direction); gl.vertexAttribPointer(locations.a_direction, 1, goog.webgl.FLOAT, - false, 32, 24); - - gl.enableVertexAttribArray(locations.a_instruction); - gl.vertexAttribPointer(locations.a_instruction, 1, goog.webgl.FLOAT, - false, 32, 28); - - var round = this.state_.lineJoin === 'round' && this.state_.lineCap === 'round' ? - ol.render.webgl.LineStringInstruction.ROUND_BOTH : this.state_.lineJoin === 'round' ? - ol.render.webgl.LineStringInstruction.ROUND_JOIN : this.state_.lineCap === 'round' ? - ol.render.webgl.LineStringInstruction.ROUND_CAP : 0; + false, 28, 24); // Enable renderer specific uniforms. If clauses needed, as otherwise the compiler complains. gl.uniform4fv(locations.u_color, this.state_.strokeColor); @@ -1415,7 +1359,6 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, gl.uniform1f(locations.u_miterLimit, this.state_.miterLimit); } gl.uniform2fv(locations.u_size, size); - gl.uniform1f(locations.u_round, round); return locations; }; diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/webgllinestringdefault.glsl index d48c34eb54..b9b8dd6d06 100644 --- a/src/ol/render/webgl/webgllinestringdefault.glsl +++ b/src/ol/render/webgl/webgllinestringdefault.glsl @@ -13,53 +13,50 @@ attribute vec2 a_lastPos; attribute vec2 a_position; attribute vec2 a_nextPos; attribute float a_direction; -attribute float a_instruction; uniform mat4 u_projectionMatrix; uniform mat4 u_offsetScaleMatrix; uniform float u_lineWidth; uniform float u_miterLimit; -uniform float u_round; void main(void) { v_halfWidth = u_lineWidth / 2.0; vec2 offset; v_round = 0.0; + float direction = a_direction / abs(a_direction); vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.); - if (a_instruction == 0. || a_instruction == 4.) { + if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) { vec2 dirVect = a_nextPos - a_position; vec2 normal = normalize(vec2(-dirVect.y, dirVect.x)); - offset = v_halfWidth * normal * a_direction; - if ((a_instruction == 4. && (u_round == 7. || u_round == 9.)) || - (a_instruction == 0. && (u_round == 8. || u_round == 9.))) { + offset = v_halfWidth * normal * direction; + if (mod(a_direction, 2.0) == 0.0) { v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } - } else if (a_instruction == 1. || a_instruction == 3.) { + } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) { vec2 dirVect = a_lastPos - a_position; vec2 normal = normalize(vec2(dirVect.y, -dirVect.x)); - offset = v_halfWidth * normal * a_direction; - if ((a_instruction == 3. && (u_round == 7. || u_round == 9.)) || - (a_instruction == 1. && (u_round == 8. || u_round == 9.))) { + offset = v_halfWidth * normal * direction; + if (mod(a_direction, 2.0) == 0.0) { v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } - } else if (a_instruction == 5. || a_instruction == 6.) { + } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) { vec2 dirVect = a_nextPos - a_position; vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x)); vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos)); vec2 normal = vec2(tangent.y, -tangent.x); float miterLength = v_halfWidth / dot(normal, tmpNormal); - if (a_instruction == 6.) { - if (u_round == 7. || u_round == 9.) { - offset = normal * a_direction * miterLength; + if (mod(a_direction, 23.0) == 0.0) { + if (mod(a_direction, 2.0) == 0.0) { + offset = normal * direction * miterLength; v_round = 1.0; v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } else if (miterLength > u_miterLimit) { - offset = tmpNormal * a_direction * v_halfWidth; + offset = tmpNormal * direction * v_halfWidth; } else { - offset = normal * a_direction * miterLength; + offset = normal * direction * miterLength; } - } else if (a_instruction == 5.) { - offset = normal * a_direction * miterLength; + } else { + offset = normal * direction * miterLength; vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.); vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset; vec4 secondProjPos = projPos + defaultOffset; @@ -76,30 +73,27 @@ void main(void) { } else { projPos = thirdProjPos - defaultOffset; } - offset = tmpNormal * a_direction * v_halfWidth; + offset = tmpNormal * direction * v_halfWidth; } } - } else if (a_instruction == 2.) { - vec2 dirVect = a_position - a_nextPos; - vec2 firstNormal = normalize(dirVect); - vec2 secondNormal = vec2(firstNormal.y * a_direction, -firstNormal.x * a_direction); - vec2 hypotenuse = normalize(firstNormal - secondNormal); - vec2 normal = vec2(hypotenuse.y * a_direction, -hypotenuse.x * a_direction); - float length = sqrt(v_halfWidth * v_halfWidth * 2.0); - offset = normal * length; - if (u_round == 8. || u_round == 9.) { - v_round = 1.0; - v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); + } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) { + vec2 normal; + if (mod(a_direction, 7.0) == 0.0) { + vec2 dirVect = a_position - a_nextPos; + vec2 firstNormal = normalize(dirVect); + vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction); + vec2 hypotenuse = normalize(firstNormal - secondNormal); + normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction); + } else { + vec2 dirVect = a_position - a_lastPos; + vec2 firstNormal = normalize(dirVect); + vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction); + vec2 hypotenuse = normalize(firstNormal - secondNormal); + normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction); } - } else if (a_instruction == 10.) { - vec2 dirVect = a_position - a_lastPos; - vec2 firstNormal = normalize(dirVect); - vec2 secondNormal = vec2(-firstNormal.y * a_direction, firstNormal.x * a_direction); - vec2 hypotenuse = normalize(firstNormal - secondNormal); - vec2 normal = vec2(-hypotenuse.y * a_direction, hypotenuse.x * a_direction); float length = sqrt(v_halfWidth * v_halfWidth * 2.0); offset = normal * length; - if (u_round == 8. || u_round == 9.) { + if (mod(a_direction, 2.0) == 0.0) { v_round = 1.0; v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/webgllinestringdefaultshader.js index 5050d095f5..41ce76e74f 100644 --- a/src/ol/render/webgl/webgllinestringdefaultshader.js +++ b/src/ol/render/webgl/webgllinestringdefaultshader.js @@ -30,7 +30,7 @@ ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precisio * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec4 b;varying float c;uniform float n;uniform vec4 o;uniform vec2 p;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*p.x,(b.y+1.0)/2.0*p.y);if(length(windowCoords-gl_FragCoord.xy)>c){discard;}} gl_FragColor=o;float alpha=o.a*n;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; +ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec4 b;varying float c;uniform float l;uniform vec4 m;uniform vec2 n;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*n.x,(b.y+1.0)/2.0*n.y);if(length(windowCoords-gl_FragCoord.xy)>c){discard;}} gl_FragColor=m;float alpha=m.a*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; /** @@ -58,14 +58,14 @@ goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\nattribute float a_instruction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\nuniform float u_round;\n\nvoid main(void) {\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (a_instruction == 0. || a_instruction == 4.) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * a_direction;\n if ((a_instruction == 4. && (u_round == 7. || u_round == 9.)) ||\n (a_instruction == 0. && (u_round == 8. || u_round == 9.))) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 1. || a_instruction == 3.) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * a_direction;\n if ((a_instruction == 3. && (u_round == 7. || u_round == 9.)) ||\n (a_instruction == 1. && (u_round == 8. || u_round == 9.))) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 5. || a_instruction == 6.) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(tangent.y, -tangent.x);\n float miterLength = v_halfWidth / dot(normal, tmpNormal);\n if (a_instruction == 6.) {\n if (u_round == 7. || u_round == 9.) {\n offset = normal * a_direction * miterLength;\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * a_direction * v_halfWidth;\n } else {\n offset = normal * a_direction * miterLength;\n }\n } else if (a_instruction == 5.) {\n offset = normal * a_direction * miterLength;\n vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset;\n vec4 secondProjPos = projPos + defaultOffset;\n vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset;\n float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy);\n float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy);\n float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy);\n //TODO: Write a more accurate method for identifying sharp angles.\n if (miterSegLength > min(firstSegLength, secondSegLength)) {\n if (firstSegLength < secondSegLength) {\n dirVect = a_lastPos - a_position;\n tmpNormal = normalize(vec2(dirVect.y, -dirVect.x));\n projPos = firstProjPos - defaultOffset;\n } else {\n projPos = thirdProjPos - defaultOffset;\n }\n offset = tmpNormal * a_direction * v_halfWidth;\n }\n }\n } else if (a_instruction == 2.) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * a_direction, -firstNormal.x * a_direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n vec2 normal = vec2(hypotenuse.y * a_direction, -hypotenuse.x * a_direction);\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (u_round == 8. || u_round == 9.) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (a_instruction == 10.) {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * a_direction, firstNormal.x * a_direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n vec2 normal = vec2(-hypotenuse.y * a_direction, hypotenuse.x * a_direction);\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (u_round == 8. || u_round == 9.) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n }\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n}\n\n\n'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(tangent.y, -tangent.x);\n float miterLength = v_halfWidth / dot(normal, tmpNormal);\n if (mod(a_direction, 23.0) == 0.0) {\n if (mod(a_direction, 2.0) == 0.0) {\n offset = normal * direction * miterLength;\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n } else {\n offset = normal * direction * miterLength;\n }\n } else {\n offset = normal * direction * miterLength;\n vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset;\n vec4 secondProjPos = projPos + defaultOffset;\n vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset;\n float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy);\n float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy);\n float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy);\n //TODO: Write a more accurate method for identifying sharp angles.\n if (miterSegLength > min(firstSegLength, secondSegLength)) {\n if (firstSegLength < secondSegLength) {\n dirVect = a_lastPos - a_position;\n tmpNormal = normalize(vec2(dirVect.y, -dirVect.x));\n projPos = firstProjPos - defaultOffset;\n } else {\n projPos = thirdProjPos - defaultOffset;\n }\n offset = tmpNormal * direction * v_halfWidth;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n }\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec4 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;attribute float h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;uniform float m;void main(void){c=k/2.0;vec2 offset;a=0.0;vec4 projPos=i*vec4(e,0.,1.);if(h==0.||h==4.){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*g;if((h==4.&&(m==7.||m==9.))||(h==0.&&(m==8.||m==9.))){b=projPos+j*vec4(0.,0.,0.,0.);}} else if(h==1.||h==3.){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*g;if((h==3.&&(m==7.||m==9.))||(h==1.&&(m==8.||m==9.))){b=projPos+j*vec4(0.,0.,0.,0.);}} else if(h==5.||h==6.){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(tangent.y,-tangent.x);float miterLength=c/dot(normal,tmpNormal);if(h==6.){if(m==7.||m==9.){offset=normal*g*miterLength;a=1.0;b=projPos+j*vec4(0.,0.,0.,0.);}else if(miterLength>l){offset=tmpNormal*g*c;}else{offset=normal*g*miterLength;}} else if(h==5.){offset=normal*g*miterLength;vec4 defaultOffset=j*vec4(0.,0.,0.,0.);vec4 firstProjPos=i*vec4(d,0.,1.)+defaultOffset;vec4 secondProjPos=projPos+defaultOffset;vec4 thirdProjPos=i*vec4(f,0.,1.)+defaultOffset;float firstSegLength=distance(secondProjPos.xy,firstProjPos.xy);float secondSegLength=distance(thirdProjPos.xy,secondProjPos.xy);float miterSegLength=distance(secondProjPos.xy,vec4(projPos+j*vec4(offset,0.,0.)).xy);if(miterSegLength>min(firstSegLength,secondSegLength)){if(firstSegLength Date: Thu, 16 Jun 2016 20:47:42 +0200 Subject: [PATCH 18/88] Adding support for closed lines --- src/ol/render/webgl/imagereplay/index.js | 45 ++++++++++++++++++------ 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index c8d590348e..a13fcd2bdb 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1048,6 +1048,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord var lineCap = this.state_.lineCap === 'butt' ? 0 : this.state_.lineCap === 'square' ? 1 : 2; var closed = this.isClosed_(flatCoordinates, offset, end, stride); + var startIndex = numIndices; var lastIndex = numIndices; var lastSign = 1; //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. @@ -1063,12 +1064,20 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord //First vertex. if (i === offset) { p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; - if (flatCoordinates.length === stride * 2 && ol.array.equals(p1, p2)) { + if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { break; } if (closed) { //A closed line! Complete the circle. - tempP = [flatCoordinates[end - stride] - this.origin_[0], flatCoordinates[end - stride + 1] - this.origin_[1]]; + var j = end - stride * 2; + var startCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + tempP = [flatCoordinates[j], flatCoordinates[j + 1]]; + while (ol.array.equals(tempP, startCoord)) { + j -= stride; + tempP = [flatCoordinates[j], flatCoordinates[j + 1]]; + } + tempP[0] -= this.origin_[0]; + tempP[1] -= this.origin_[1]; } else { //Add the first two/four vertices. @@ -1103,7 +1112,8 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord } else if (i === end - stride) { //Last vertex. if (closed) { - p2 = [flatCoordinates[offset] - this.origin_[0], flatCoordinates[offset + 1] - this.origin_[1]]; + //Same as the first vertex. + break; } else { p2 = undefined; //Note, that the third case will never happen, we just have to assure the compiler, @@ -1150,7 +1160,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord if (ol.array.equals(p1, p2)) continue; p0 = tempP || p0; tempP = undefined; - //The sign of the area determines the line segment's orientation. + var sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) ? 1 : -1; @@ -1163,13 +1173,15 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord numVertices = this.addVertices_(p0, p1, p2, -sign * ol.render.webgl.LineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastIndex - 1; - this.indices_[numIndices++] = lastIndex; + 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 + 2; + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; + } this.indices_[numIndices++] = n; this.indices_[numIndices++] = n + 2; @@ -1188,6 +1200,17 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord this.indices_[numIndices++] = n; } } + + if (closed) { + //Link the last triangle/rhombus to the first one. + this.indices_[numIndices++] = lastIndex; + this.indices_[numIndices++] = startIndex + 2; + this.indices_[numIndices++] = startIndex; + + this.indices_[numIndices++] = startIndex; + this.indices_[numIndices++] = lastIndex - 1; + this.indices_[numIndices++] = lastIndex; + } }; /** @@ -1223,7 +1246,7 @@ ol.render.webgl.LineStringReplay.prototype.addVertices_ = function(p0, p1, p2, p ol.render.webgl.LineStringReplay.prototype.isClosed_ = function(flatCoordinates, offset, end, stride) { var lastCoord = end - stride; if (flatCoordinates[offset] === flatCoordinates[lastCoord] && - flatCoordinates[offset + 1] === flatCoordinates[lastCoord + 1]) { + flatCoordinates[offset + 1] === flatCoordinates[lastCoord + 1] && (end - offset) / stride > 3) { return true; } return false; From 99e4009b190cb8755de3e493fad9fbb6c78a8525 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 17 Jun 2016 11:20:56 +0200 Subject: [PATCH 19/88] Minor improvements ol.render.webgl.LineStringReplay.startIndices_ is initialized with a 0, and the end index of a valid line is added in every valid iteration. drawElements_ is refactored for a clearer code. --- src/ol/render/webgl/imagereplay/index.js | 60 ++++++++++-------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index a13fcd2bdb..74980760ca 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -188,7 +188,7 @@ ol.render.webgl.Replay.prototype.replay = function(context, context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); // bind the indices buffer - goog.asserts.assert(!goog.isNull(this.indicesBuffer_), + goog.asserts.assert(this.indicesBuffer_, 'indicesBuffer must not be null'); context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); @@ -240,16 +240,19 @@ ol.render.webgl.Replay.prototype.replay = function(context, /** * @private * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. * @param {number} start Start index. * @param {number} end End index. - * @param {number} elementType Element type. - * @param {number} elementSize Element Size. */ ol.render.webgl.Replay.prototype.drawElements_ = function( - gl, start, end, elementType, elementSize) { + 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(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); + gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes); }; /** @@ -710,20 +713,16 @@ ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippe var groupIndices = hitDetection ? this.hitDetectionGroupIndices_ : this.groupIndices_; ol.DEBUG && console.assert(textures.length === groupIndices.length, 'number of textures and groupIndeces match'); - var elementType = context.hasOESElementIndexUint ? - ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; - var elementSize = context.hasOESElementIndexUint ? 4 : 2; if (!ol.obj.isEmpty(skippedFeaturesHash)) { this.drawReplaySkipping_( - gl, skippedFeaturesHash, textures, groupIndices, - elementType, elementSize); + 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, start, end, elementType, elementSize); + this.drawElements_(gl, context, start, end); start = end; } } @@ -750,15 +749,14 @@ ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippe * * @private * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. * @param {Object.} skippedFeaturesHash Ids of features * to skip. * @param {Array.} textures Textures. * @param {Array.} groupIndices Texture group indices. - * @param {number} elementType Element type. - * @param {number} elementSize Element Size. */ -ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, skippedFeaturesHash, textures, groupIndices, - elementType, elementSize) { +ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash, textures, + groupIndices) { var featureIndex = 0; var i, ii; @@ -778,7 +776,7 @@ ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, skipped // feature should be skipped if (start !== end) { // draw the features so far - this.drawElements_(gl, start, end, elementType, elementSize); + this.drawElements_(gl, context, start, end); } // continue with the next feature start = (featureIndex === this.startIndices_.length - 1) ? @@ -795,7 +793,7 @@ ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, skipped 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, start, end, elementType, elementSize); + this.drawElements_(gl, context, start, end); } } }; @@ -868,9 +866,6 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function ol.DEBUG && console.assert(this.hitDetectionTextures_.length === this.hitDetectionGroupIndices_.length, 'number of hitDetectionTextures and hitDetectionGroupIndices match'); - var elementType = context.hasOESElementIndexUint ? - ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; - var elementSize = context.hasOESElementIndexUint ? 4 : 2; var i, groupStart, start, end, feature, featureUid; var featureIndex = this.startIndices_.length - 1; @@ -892,7 +887,7 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function /** @type {Array} */ (opt_hitExtent), feature.getGeometry().getExtent()))) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements_(gl, start, end, elementType, elementSize); + this.drawElements_(gl, context, start, end); var result = featureCallback(feature); if (result) { @@ -1023,6 +1018,8 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { miterLimit: undefined }; + this.startIndices_ = [0]; + }; ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); @@ -1260,10 +1257,10 @@ ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringG var flatCoordinates = lineStringGeometry.getFlatCoordinates(); var stride = lineStringGeometry.getStride(); if (flatCoordinates.length > stride) { - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); this.drawCoordinates_( flatCoordinates, 0, flatCoordinates.length, stride); + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); } }; @@ -1272,20 +1269,21 @@ ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringG * @inheritDoc */ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); + var indexCount = this.indices_.length; var lineStringGeometries = multiLineStringGeometry.getLineStrings(); var i, ii; for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); var stride = lineStringGeometries[i].getStride(); if (flatCoordinates.length > stride) { - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); this.drawCoordinates_( flatCoordinates, 0, flatCoordinates.length, stride); } } + if (this.indices_.length > indexCount) { + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); + } }; @@ -1293,8 +1291,6 @@ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiL * @param {ol.webgl.Context} context Context. **/ ol.render.webgl.LineStringReplay.prototype.finish = function(context) { - this.startIndices_.push(this.indices_.length); - // create, bind, and populate the vertices buffer this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); @@ -1395,15 +1391,11 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, * @param {boolean} hitDetection Hit detection mode. */ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { - var elementType = context.hasOESElementIndexUint ? - goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; - var elementSize = context.hasOESElementIndexUint ? 4 : 2; - if (!goog.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { var end = this.startIndices_[this.startIndices_.length - 1]; - this.drawElements_(gl, 0, end, elementType, elementSize); + this.drawElements_(gl, context, 0, end); } }; From 8c561a45b950f4bf3c47bee21b942359473e1017 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 17 Jun 2016 12:58:32 +0200 Subject: [PATCH 20/88] Inspect linestring validity before drawing --- src/ol/render/webgl/imagereplay/index.js | 292 +++++++++++++---------- 1 file changed, 164 insertions(+), 128 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 74980760ca..5e8eff36a2 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1052,161 +1052,164 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord //We rotate those points, thus every point is RTE corrected only once. var p0, p1, p2, tempP; - for (i = offset, ii = end; i < ii; i += stride) { + if (this.isValid_(flatCoordinates, offset, end, stride, closed)) { - var n = numVertices / 7; + for (i = offset, ii = end; i < ii; i += stride) { - tempP = tempP || p1; - p1 = p2 || [flatCoordinates[i] - this.origin_[0], flatCoordinates[i + 1] - this.origin_[1]]; - //First vertex. - if (i === offset) { - p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; - if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { - break; - } - if (closed) { - //A closed line! Complete the circle. - var j = end - stride * 2; - var startCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; - tempP = [flatCoordinates[j], flatCoordinates[j + 1]]; - while (ol.array.equals(tempP, startCoord)) { - j -= stride; + var n = numVertices / 7; + + tempP = tempP || p1; + p1 = p2 || [flatCoordinates[i] - this.origin_[0], flatCoordinates[i + 1] - this.origin_[1]]; + //First vertex. + if (i === offset) { + p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; + if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { + break; + } + if (closed) { + //A closed line! Complete the circle. + var j = end - stride * 2; + var startCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; tempP = [flatCoordinates[j], flatCoordinates[j + 1]]; + while (ol.array.equals(tempP, startCoord)) { + j -= stride; + tempP = [flatCoordinates[j], flatCoordinates[j + 1]]; + } + tempP[0] -= this.origin_[0]; + tempP[1] -= this.origin_[1]; + } else { + //Add the first two/four vertices. + + if (lineCap) { + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = n + 1; + + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 3; + this.indices_[numIndices++] = n + 2; + + n = n + 2; + } + + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + lastIndex = n + 1; + + continue; } - tempP[0] -= this.origin_[0]; - tempP[1] -= this.origin_[1]; - } else { - //Add the first two/four vertices. + } else if (i === end - stride) { + //Last vertex. + if (closed) { + //Same as the first vertex. + break; + } else { + p2 = undefined; + //Note, that the third case will never happen, we just have to assure the compiler, + //p0 is always an array of nums. + p0 = tempP || p0 || [0, 0]; - if (lineCap) { - numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); - this.indices_[numIndices++] = n + 2; this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = lastIndex - 1; + this.indices_[numIndices++] = lastIndex; + this.indices_[numIndices++] = lastIndex; this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n; - n = n + 2; + if (lineCap) { + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = n + 1; + + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 3; + this.indices_[numIndices++] = n + 2; + + } + + break; } - - numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - - lastIndex = n + 1; - - continue; - } - } else if (i === end - stride) { - //Last vertex. - if (closed) { - //Same as the first vertex. - break; } else { - p2 = undefined; - //Note, that the third case will never happen, we just have to assure the compiler, - //p0 is always an array of nums. - p0 = tempP || p0 || [0, 0]; + p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; + } + //Sort out duplicate points. + if (ol.array.equals(p1, p2)) continue; + p0 = tempP || p0; + tempP = undefined; - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); + var 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, [0, 0], - -lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.LineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + if (i > offset) { this.indices_[numIndices++] = n; this.indices_[numIndices++] = lastIndex - 1; this.indices_[numIndices++] = lastIndex; - this.indices_[numIndices++] = lastIndex; - this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 2; this.indices_[numIndices++] = n; - - if (lineCap) { - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); - - numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n + 2; - - } - - break; + this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; } - } else { - p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; - } - //Sort out duplicate points. - if (ol.array.equals(p1, p2)) continue; - p0 = tempP || p0; - tempP = undefined; - var sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) - ? 1 : -1; - - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.LineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); - - if (i > offset) { this.indices_[numIndices++] = n; + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n + 1; + + lastIndex = n + 2; + lastSign = sign; + + //Add miter + if (lineJoin) { + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.MITER_TOP * lineJoin, numVertices); + + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 3; + this.indices_[numIndices++] = n; + } + } + + if (closed) { + //Link the last triangle/rhombus to the first one. + this.indices_[numIndices++] = lastIndex; + this.indices_[numIndices++] = startIndex + 2; + this.indices_[numIndices++] = startIndex; + + this.indices_[numIndices++] = startIndex; this.indices_[numIndices++] = lastIndex - 1; this.indices_[numIndices++] = lastIndex; - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; } - - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n + 1; - - lastIndex = n + 2; - lastSign = sign; - - //Add miter - if (lineJoin) { - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.MITER_TOP * lineJoin, numVertices); - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n; - } - } - - if (closed) { - //Link the last triangle/rhombus to the first one. - this.indices_[numIndices++] = lastIndex; - this.indices_[numIndices++] = startIndex + 2; - this.indices_[numIndices++] = startIndex; - - this.indices_[numIndices++] = startIndex; - this.indices_[numIndices++] = lastIndex - 1; - this.indices_[numIndices++] = lastIndex; } }; @@ -1249,6 +1252,39 @@ ol.render.webgl.LineStringReplay.prototype.isClosed_ = function(flatCoordinates, return false; }; +/** + * 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. + * @param {boolean} closed The linestring is a boundary. + * @return {boolean} The linestring can be drawn. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, offset, end, stride, + closed) { + var uniqueCoords = [[flatCoordinates[offset], flatCoordinates[offset + 1]]]; + var minUnique = closed ? 3 : 2; + var i, ii, j; + for (i = offset + stride, ii = end; i < ii; i += stride) { + var currentCoords = [flatCoordinates[i], flatCoordinates[i + 1]]; + for (j = 0; j < uniqueCoords.length; ++j) { + if (ol.array.equals(currentCoords, uniqueCoords[j])) { + j = -1; + break; + } + } + if (j > -1) { + uniqueCoords.push(currentCoords); + } + if (uniqueCoords.length >= minUnique) { + return true; + } + } + return false; +}; + /** * @inheritDoc From eba006b79683e38542f74f91710260c00ad27759 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 17 Jun 2016 18:11:18 +0200 Subject: [PATCH 21/88] Falling back to bevel when exceeding miterLimit --- src/ol/render/webgl/webgllinestringdefault.glsl | 6 ++---- src/ol/render/webgl/webgllinestringdefaultshader.js | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/webgllinestringdefault.glsl index b9b8dd6d06..635d7856b4 100644 --- a/src/ol/render/webgl/webgllinestringdefault.glsl +++ b/src/ol/render/webgl/webgllinestringdefault.glsl @@ -43,17 +43,15 @@ void main(void) { vec2 dirVect = a_nextPos - a_position; vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x)); vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos)); - vec2 normal = vec2(tangent.y, -tangent.x); + vec2 normal = vec2(-tangent.y, tangent.x); float miterLength = v_halfWidth / dot(normal, tmpNormal); if (mod(a_direction, 23.0) == 0.0) { + offset = normal * direction * miterLength; if (mod(a_direction, 2.0) == 0.0) { - offset = normal * direction * miterLength; v_round = 1.0; v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } else if (miterLength > u_miterLimit) { offset = tmpNormal * direction * v_halfWidth; - } else { - offset = normal * direction * miterLength; } } else { offset = normal * direction * miterLength; diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/webgllinestringdefaultshader.js index 41ce76e74f..9e9def9550 100644 --- a/src/ol/render/webgl/webgllinestringdefaultshader.js +++ b/src/ol/render/webgl/webgllinestringdefaultshader.js @@ -58,14 +58,14 @@ goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(tangent.y, -tangent.x);\n float miterLength = v_halfWidth / dot(normal, tmpNormal);\n if (mod(a_direction, 23.0) == 0.0) {\n if (mod(a_direction, 2.0) == 0.0) {\n offset = normal * direction * miterLength;\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n } else {\n offset = normal * direction * miterLength;\n }\n } else {\n offset = normal * direction * miterLength;\n vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset;\n vec4 secondProjPos = projPos + defaultOffset;\n vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset;\n float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy);\n float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy);\n float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy);\n //TODO: Write a more accurate method for identifying sharp angles.\n if (miterSegLength > min(firstSegLength, secondSegLength)) {\n if (firstSegLength < secondSegLength) {\n dirVect = a_lastPos - a_position;\n tmpNormal = normalize(vec2(dirVect.y, -dirVect.x));\n projPos = firstProjPos - defaultOffset;\n } else {\n projPos = thirdProjPos - defaultOffset;\n }\n offset = tmpNormal * direction * v_halfWidth;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n }\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n}\n\n\n'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = v_halfWidth / dot(normal, tmpNormal);\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n offset = normal * direction * miterLength;\n vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset;\n vec4 secondProjPos = projPos + defaultOffset;\n vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset;\n float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy);\n float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy);\n float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy);\n //TODO: Write a more accurate method for identifying sharp angles.\n if (miterSegLength > min(firstSegLength, secondSegLength)) {\n if (firstSegLength < secondSegLength) {\n dirVect = a_lastPos - a_position;\n tmpNormal = normalize(vec2(dirVect.y, -dirVect.x));\n projPos = firstProjPos - defaultOffset;\n } else {\n projPos = thirdProjPos - defaultOffset;\n }\n offset = tmpNormal * direction * v_halfWidth;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n }\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec4 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){c=j/2.0;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;if(mod(g,2.0)==0.0){b=projPos+i*vec4(0.,0.,0.,0.);}} else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;if(mod(g,2.0)==0.0){b=projPos+i*vec4(0.,0.,0.,0.);}} else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(tangent.y,-tangent.x);float miterLength=c/dot(normal,tmpNormal);if(mod(g,23.0)==0.0){if(mod(g,2.0)==0.0){offset=normal*direction*miterLength;a=1.0;b=projPos+i*vec4(0.,0.,0.,0.);}else if(miterLength>k){offset=tmpNormal*direction*c;}else{offset=normal*direction*miterLength;}} else{offset=normal*direction*miterLength;vec4 defaultOffset=i*vec4(0.,0.,0.,0.);vec4 firstProjPos=h*vec4(d,0.,1.)+defaultOffset;vec4 secondProjPos=projPos+defaultOffset;vec4 thirdProjPos=h*vec4(f,0.,1.)+defaultOffset;float firstSegLength=distance(secondProjPos.xy,firstProjPos.xy);float secondSegLength=distance(thirdProjPos.xy,secondProjPos.xy);float miterSegLength=distance(secondProjPos.xy,vec4(projPos+i*vec4(offset,0.,0.)).xy);if(miterSegLength>min(firstSegLength,secondSegLength)){if(firstSegLength Date: Tue, 21 Jun 2016 12:57:45 +0200 Subject: [PATCH 22/88] Fix rendering of boundaries. --- src/ol/render/webgl/imagereplay/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 5e8eff36a2..a0b5306d8f 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1202,7 +1202,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord if (closed) { //Link the last triangle/rhombus to the first one. - this.indices_[numIndices++] = lastIndex; + this.indices_[numIndices++] = lastSign > 0 ? lastIndex : lastIndex - 1; this.indices_[numIndices++] = startIndex + 2; this.indices_[numIndices++] = startIndex; From 4be8de62ae248114c2f12b3a40f1556e266f6f4c Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 21 Jun 2016 16:02:34 +0200 Subject: [PATCH 23/88] Refactor linestring geom validation Now it only focuses on issues not addressed by ol.geom.flat.simplify methods. --- src/ol/render/webgl/imagereplay/index.js | 294 ++++++++++------------- 1 file changed, 133 insertions(+), 161 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index a0b5306d8f..588e1a00d3 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1050,166 +1050,149 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord var lastSign = 1; //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. //We rotate those points, thus every point is RTE corrected only once. - var p0, p1, p2, tempP; + var p0, p1, p2; - if (this.isValid_(flatCoordinates, offset, end, stride, closed)) { + for (i = offset, ii = end; i < ii; i += stride) { - for (i = offset, ii = end; i < ii; i += stride) { + var n = numVertices / 7; - var n = numVertices / 7; - - tempP = tempP || p1; - p1 = p2 || [flatCoordinates[i] - this.origin_[0], flatCoordinates[i + 1] - this.origin_[1]]; - //First vertex. - if (i === offset) { - p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; - if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { - break; - } - if (closed) { - //A closed line! Complete the circle. - var j = end - stride * 2; - var startCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; - tempP = [flatCoordinates[j], flatCoordinates[j + 1]]; - while (ol.array.equals(tempP, startCoord)) { - j -= stride; - tempP = [flatCoordinates[j], flatCoordinates[j + 1]]; - } - tempP[0] -= this.origin_[0]; - tempP[1] -= this.origin_[1]; - } else { - //Add the first two/four vertices. - - if (lineCap) { - numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); - - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n + 2; - - n = n + 2; - } - - numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - - lastIndex = n + 1; - - continue; - } - } else if (i === end - stride) { - //Last vertex. - if (closed) { - //Same as the first vertex. - break; - } else { - p2 = undefined; - //Note, that the third case will never happen, we just have to assure the compiler, - //p0 is always an array of nums. - p0 = tempP || p0 || [0, 0]; - - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); - - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastIndex - 1; - this.indices_[numIndices++] = lastIndex; - - this.indices_[numIndices++] = lastIndex; - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n; - - if (lineCap) { - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); - - numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n + 2; - - } - - break; - } - } else { - p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; + p0 = p1; + p1 = p2 || [flatCoordinates[i] - this.origin_[0], flatCoordinates[i + 1] - this.origin_[1]]; + //First vertex. + if (i === offset) { + p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; + if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { + break; } - //Sort out duplicate points. - if (ol.array.equals(p1, p2)) continue; - p0 = tempP || p0; - tempP = undefined; + if (closed) { + //A closed line! Complete the circle. + p0 = [flatCoordinates[end - stride * 2] - this.origin_[0], + flatCoordinates[end - stride * 2 + 1] - this.origin_[1]]; + } else { + //Add the first two/four vertices. - var sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) - ? 1 : -1; + if (lineCap) { + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = n + 1; - numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.LineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 3; + this.indices_[numIndices++] = n + 2; + + } + + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + lastIndex = n + 3; + + continue; + } + } else if (i === end - stride) { + //Last vertex. + if (closed) { + //Same as the first vertex. + break; + } else { + //For the compiler not to complain. This will be never [0, 0]. + p0 = p0 || [0, 0]; + + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); - if (i > offset) { this.indices_[numIndices++] = n; this.indices_[numIndices++] = lastIndex - 1; this.indices_[numIndices++] = lastIndex; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; - } - - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n + 1; - - lastIndex = n + 2; - lastSign = sign; - - //Add miter - if (lineJoin) { - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.MITER_TOP * lineJoin, numVertices); - + this.indices_[numIndices++] = 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.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = n + 1; + + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 3; + this.indices_[numIndices++] = n + 2; + + } + + break; } + } else { + p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; } - if (closed) { - //Link the last triangle/rhombus to the first one. - this.indices_[numIndices++] = lastSign > 0 ? lastIndex : lastIndex - 1; - this.indices_[numIndices++] = startIndex + 2; - this.indices_[numIndices++] = startIndex; + var sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) + ? 1 : -1; - this.indices_[numIndices++] = startIndex; + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.LineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + if (i > offset) { + this.indices_[numIndices++] = n; this.indices_[numIndices++] = lastIndex - 1; this.indices_[numIndices++] = lastIndex; + + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; } + + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n + 1; + + lastIndex = n + 2; + lastSign = sign; + + //Add miter + if (lineJoin) { + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.MITER_TOP * lineJoin, numVertices); + + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 3; + this.indices_[numIndices++] = n; + } + } + + if (closed) { + //Link the last triangle/rhombus to the first one. + this.indices_[numIndices++] = lastSign > 0 ? lastIndex : lastIndex - 1; + this.indices_[numIndices++] = startIndex + 2; + this.indices_[numIndices++] = startIndex; + + this.indices_[numIndices++] = startIndex; + this.indices_[numIndices++] = lastIndex - 1; + this.indices_[numIndices++] = lastIndex; } }; @@ -1258,31 +1241,20 @@ ol.render.webgl.LineStringReplay.prototype.isClosed_ = function(flatCoordinates, * @param {number} offset Offset. * @param {number} end End. * @param {number} stride Stride. - * @param {boolean} closed The linestring is a boundary. * @return {boolean} The linestring can be drawn. * @private */ -ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, offset, end, stride, - closed) { - var uniqueCoords = [[flatCoordinates[offset], flatCoordinates[offset + 1]]]; - var minUnique = closed ? 3 : 2; - var i, ii, j; - for (i = offset + stride, ii = end; i < ii; i += stride) { - var currentCoords = [flatCoordinates[i], flatCoordinates[i + 1]]; - for (j = 0; j < uniqueCoords.length; ++j) { - if (ol.array.equals(currentCoords, uniqueCoords[j])) { - j = -1; - break; - } - } - if (j > -1) { - uniqueCoords.push(currentCoords); - } - if (uniqueCoords.length >= minUnique) { - return true; - } +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 false; + + return true; }; @@ -1292,7 +1264,7 @@ ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { var flatCoordinates = lineStringGeometry.getFlatCoordinates(); var stride = lineStringGeometry.getStride(); - if (flatCoordinates.length > stride) { + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { this.drawCoordinates_( flatCoordinates, 0, flatCoordinates.length, stride); this.startIndices_.push(this.indices_.length); @@ -1311,7 +1283,7 @@ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiL for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); var stride = lineStringGeometries[i].getStride(); - if (flatCoordinates.length > stride) { + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { this.drawCoordinates_( flatCoordinates, 0, flatCoordinates.length, stride); } From e64549c50c803be64bd01fdb5ef99ba0b0a44100 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 22 Jun 2016 12:09:02 +0200 Subject: [PATCH 24/88] Fix weird behaviour of boundaries With drawElements, it seems like we cannot connect an old index with a much fresher one, as it will produce weird errors. --- src/ol/render/webgl/imagereplay/index.js | 30 +++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 588e1a00d3..54a187a368 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1045,7 +1045,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord var lineCap = this.state_.lineCap === 'butt' ? 0 : this.state_.lineCap === 'square' ? 1 : 2; var closed = this.isClosed_(flatCoordinates, offset, end, stride); - var startIndex = numIndices; + 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. @@ -1054,7 +1054,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord for (i = offset, ii = end; i < ii; i += stride) { - var n = numVertices / 7; + n = numVertices / 7; p0 = p1; p1 = p2 || [flatCoordinates[i] - this.origin_[0], flatCoordinates[i + 1] - this.origin_[1]]; @@ -1068,6 +1068,8 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord //A closed line! Complete the circle. p0 = [flatCoordinates[end - stride * 2] - this.origin_[0], flatCoordinates[end - stride * 2 + 1] - this.origin_[1]]; + + startCoords = p2; } else { //Add the first two/four vertices. @@ -1102,9 +1104,10 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord //Last vertex. if (closed) { //Same as the first vertex. + p2 = startCoords; break; } else { - //For the compiler not to complain. This will be never [0, 0]. + //For the compiler not to complain. This will never be [0, 0]. p0 = p0 || [0, 0]; numVertices = this.addVertices_(p0, p1, [0, 0], @@ -1144,7 +1147,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; } - var sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) + 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, @@ -1186,13 +1189,24 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord if (closed) { //Link the last triangle/rhombus to the first one. - this.indices_[numIndices++] = lastSign > 0 ? lastIndex : lastIndex - 1; - this.indices_[numIndices++] = startIndex + 2; - this.indices_[numIndices++] = startIndex; + //n will never be numVertices / 7 here. However, the compiler complains otherwise. + n = n || numVertices / 7; + sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) + ? 1 : -1; - this.indices_[numIndices++] = startIndex; + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.LineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + this.indices_[numIndices++] = n; this.indices_[numIndices++] = lastIndex - 1; this.indices_[numIndices++] = lastIndex; + + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; } }; From 3f828248b93b06ae5f9cc298fbd11c28028b70e7 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 22 Jun 2016 16:19:47 +0200 Subject: [PATCH 25/88] Rework isClosed with added tests --- src/ol/geom/flat/topologyflatgeom.js | 20 +++++++++++ src/ol/render/webgl/imagereplay/index.js | 22 ++----------- .../ol/geom/flat/topologyflatgeom.test.js | 33 +++++++++++++++++++ 3 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 src/ol/geom/flat/topologyflatgeom.js create mode 100644 test/spec/ol/geom/flat/topologyflatgeom.test.js diff --git a/src/ol/geom/flat/topologyflatgeom.js b/src/ol/geom/flat/topologyflatgeom.js new file mode 100644 index 0000000000..a1b9f053f5 --- /dev/null +++ b/src/ol/geom/flat/topologyflatgeom.js @@ -0,0 +1,20 @@ +goog.provide('ol.geom.flat.topology'); + +goog.require('ol.geom.flat.area'); + +/** + * Check if the linestring is a boundary. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} The linestring is a boundary. + */ +ol.geom.flat.topology.lineStringIsClosed = function(flatCoordinates, offset, end, stride) { + var lastCoord = end - stride; + if (flatCoordinates[offset] === flatCoordinates[lastCoord] && + flatCoordinates[offset + 1] === flatCoordinates[lastCoord + 1] && (end - offset) / stride > 3) { + return !!ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride); + } + return false; +}; diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 54a187a368..3c1633341f 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -10,6 +10,8 @@ goog.require('ol.ext.earcut'); goog.require('ol.extent'); goog.require('ol.obj'); goog.require('ol.render.ReplayGroup'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.topology'); goog.require('ol.render.VectorContext'); goog.require('ol.render.replay'); goog.require('ol.render.webgl.imagereplay.defaultshader'); @@ -1044,7 +1046,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord this.state_.lineJoin === 'miter' ? 1 : 2; var lineCap = this.state_.lineCap === 'butt' ? 0 : this.state_.lineCap === 'square' ? 1 : 2; - var closed = this.isClosed_(flatCoordinates, offset, end, stride); + var closed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, offset, end, stride); var startCoords, sign, n; var lastIndex = numIndices; var lastSign = 1; @@ -1231,24 +1233,6 @@ ol.render.webgl.LineStringReplay.prototype.addVertices_ = function(p0, p1, p2, p return numVertices; }; -/** - * Check if the linestring is a boundary. - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {boolean} The linestring is a boundary. - * @private - */ -ol.render.webgl.LineStringReplay.prototype.isClosed_ = function(flatCoordinates, offset, end, stride) { - var lastCoord = end - stride; - if (flatCoordinates[offset] === flatCoordinates[lastCoord] && - flatCoordinates[offset + 1] === flatCoordinates[lastCoord + 1] && (end - offset) / stride > 3) { - return true; - } - return false; -}; - /** * Check if the linestring can be drawn (i. e. valid). * @param {Array.} flatCoordinates Flat coordinates. diff --git a/test/spec/ol/geom/flat/topologyflatgeom.test.js b/test/spec/ol/geom/flat/topologyflatgeom.test.js new file mode 100644 index 0000000000..dd93d83ac3 --- /dev/null +++ b/test/spec/ol/geom/flat/topologyflatgeom.test.js @@ -0,0 +1,33 @@ +goog.provide('ol.test.geom.flat.topology'); + +describe('ol.geom.flat.topology', function() { + + describe('ol.geom.flat.topology.lineStringIsClosed', function() { + + it('identifies closed lines aka boundaries', function() { + var flatCoordinates = [0, 0, 3, 0, 0, 3, 0, 0]; + var isClosed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, 0, flatCoordinates.length, 2); + expect(isClosed).to.be(true); + }); + + it('identifies regular linestrings', function() { + var flatCoordinates = [0, 0, 3, 0, 0, 3, 5, 2]; + var isClosed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, 0, flatCoordinates.length, 2); + expect(isClosed).to.be(false); + }); + + it('identifies degenerate boundaries', function() { + var flatCoordinates = [0, 0, 3, 0, 0, 0]; + var isClosed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, 0, flatCoordinates.length, 2); + expect(isClosed).to.be(false); + + flatCoordinates = [0, 0, 1, 1, 3, 3, 5, 5, 0, 0]; + isClosed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, 0, flatCoordinates.length, 2); + expect(isClosed).to.be(false); + }); + + }); + +}); + +goog.require('ol.geom.flat.topology'); From c9c970303f492b82c9e875d7ae27d2849a1a74e8 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Sun, 26 Jun 2016 13:04:43 +0200 Subject: [PATCH 26/88] Handling sharp angles in linestring GLSL --- .../render/webgl/webgllinestringdefault.glsl | 57 ++++++++++++------- .../webgl/webgllinestringdefaultshader.js | 4 +- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/webgllinestringdefault.glsl index 635d7856b4..fbf287894b 100644 --- a/src/ol/render/webgl/webgllinestringdefault.glsl +++ b/src/ol/render/webgl/webgllinestringdefault.glsl @@ -20,6 +20,7 @@ uniform float u_lineWidth; uniform float u_miterLimit; void main(void) { + bool degenerate = false; v_halfWidth = u_lineWidth / 2.0; vec2 offset; v_round = 0.0; @@ -44,7 +45,7 @@ void main(void) { vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x)); vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos)); vec2 normal = vec2(-tangent.y, tangent.x); - float miterLength = v_halfWidth / dot(normal, tmpNormal); + float miterLength = abs(v_halfWidth / dot(normal, tmpNormal)); if (mod(a_direction, 23.0) == 0.0) { offset = normal * direction * miterLength; if (mod(a_direction, 2.0) == 0.0) { @@ -54,24 +55,36 @@ void main(void) { offset = tmpNormal * direction * v_halfWidth; } } else { - offset = normal * direction * miterLength; - vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.); - vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset; - vec4 secondProjPos = projPos + defaultOffset; - vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset; - float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy); - float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy); - float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy); - //TODO: Write a more accurate method for identifying sharp angles. - if (miterSegLength > min(firstSegLength, secondSegLength)) { - if (firstSegLength < secondSegLength) { - dirVect = a_lastPos - a_position; - tmpNormal = normalize(vec2(dirVect.y, -dirVect.x)); - projPos = firstProjPos - defaultOffset; - } else { - projPos = thirdProjPos - defaultOffset; - } - offset = tmpNormal * direction * v_halfWidth; + dirVect = a_lastPos - a_position; + vec2 longOffset, shortOffset, longVertex; + vec4 shortProjVertex; + if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) { + longOffset = tmpNormal * direction * v_halfWidth; + shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth; + longVertex = a_nextPos; + shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.); + } else { + shortOffset = tmpNormal * direction * v_halfWidth; + longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth; + longVertex = a_lastPos; + shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.); + } + //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/). + vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.); + vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.); + vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.); + vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.); + float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y); + float epsilon = 0.000000000001; + float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom; + float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom; + if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) { + gl_Position = shortProjVertex; + gl_Position.x = p1.x + firstU * (p2.x - p1.x); + gl_Position.y = p1.y + firstU * (p2.y - p1.y); + degenerate = true; + } else { + offset = normal * direction * miterLength; } } } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) { @@ -96,8 +109,10 @@ void main(void) { v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } } - vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.); - gl_Position = projPos + offsets; + if (!degenerate) { + vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.); + gl_Position = projPos + offsets; + } } diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/webgllinestringdefaultshader.js index 9e9def9550..e69ebcb62b 100644 --- a/src/ol/render/webgl/webgllinestringdefaultshader.js +++ b/src/ol/render/webgl/webgllinestringdefaultshader.js @@ -58,14 +58,14 @@ goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = v_halfWidth / dot(normal, tmpNormal);\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n offset = normal * direction * miterLength;\n vec4 defaultOffset = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n vec4 firstProjPos = u_projectionMatrix * vec4(a_lastPos, 0., 1.) + defaultOffset;\n vec4 secondProjPos = projPos + defaultOffset;\n vec4 thirdProjPos = u_projectionMatrix * vec4(a_nextPos, 0., 1.) + defaultOffset;\n float firstSegLength = distance(secondProjPos.xy, firstProjPos.xy);\n float secondSegLength = distance(thirdProjPos.xy, secondProjPos.xy);\n float miterSegLength = distance(secondProjPos.xy, vec4(projPos + u_offsetScaleMatrix * vec4(offset, 0., 0.)).xy);\n //TODO: Write a more accurate method for identifying sharp angles.\n if (miterSegLength > min(firstSegLength, secondSegLength)) {\n if (firstSegLength < secondSegLength) {\n dirVect = a_lastPos - a_position;\n tmpNormal = normalize(vec2(dirVect.y, -dirVect.x));\n projPos = firstProjPos - defaultOffset;\n } else {\n projPos = thirdProjPos - defaultOffset;\n }\n offset = tmpNormal * direction * v_halfWidth;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n }\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n}\n\n\n'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec4 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){c=j/2.0;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;if(mod(g,2.0)==0.0){b=projPos+i*vec4(0.,0.,0.,0.);}} else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;if(mod(g,2.0)==0.0){b=projPos+i*vec4(0.,0.,0.,0.);}} else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=c/dot(normal,tmpNormal);if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;b=projPos+i*vec4(0.,0.,0.,0.);}else if(miterLength>k){offset=tmpNormal*direction*c;}} else{offset=normal*direction*miterLength;vec4 defaultOffset=i*vec4(0.,0.,0.,0.);vec4 firstProjPos=h*vec4(d,0.,1.)+defaultOffset;vec4 secondProjPos=projPos+defaultOffset;vec4 thirdProjPos=h*vec4(f,0.,1.)+defaultOffset;float firstSegLength=distance(secondProjPos.xy,firstProjPos.xy);float secondSegLength=distance(thirdProjPos.xy,secondProjPos.xy);float miterSegLength=distance(secondProjPos.xy,vec4(projPos+i*vec4(offset,0.,0.)).xy);if(miterSegLength>min(firstSegLength,secondSegLength)){if(firstSegLength Date: Sun, 26 Jun 2016 22:50:10 +0200 Subject: [PATCH 27/88] Eliminate overdraw with a nice little trick --- src/ol/render/webgl/imagereplay/index.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 3c1633341f..b56ab23544 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1397,12 +1397,24 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, * @param {boolean} hitDetection Hit detection mode. */ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = gl.getParameter(gl.DEPTH_FUNC); + var tmpDepthMask = gl.getParameter(gl.DEPTH_WRITEMASK); + + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); if (!goog.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { var end = this.startIndices_[this.startIndices_.length - 1]; this.drawElements_(gl, context, 0, end); } + gl.clear(gl.DEPTH_BUFFER_BIT); + gl.disable(gl.DEPTH_TEST); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); }; /** From b0d11391b235a825668bb753a28f4f11e37b680a Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 27 Jun 2016 11:41:35 +0200 Subject: [PATCH 28/88] Fix rounding problems Fragment shader does not know about the projection matrix, thus it has to take the pixel ratio into account. --- src/ol/render/webgl/imagereplay/index.js | 11 ++++++++--- src/ol/render/webgl/webgllinestringdefault.glsl | 17 ++++++----------- .../webgl/webgllinestringdefaultshader.js | 14 ++++++++++---- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index b56ab23544..daacc18459 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -194,7 +194,7 @@ ol.render.webgl.Replay.prototype.replay = function(context, 'indicesBuffer must not be null'); context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - var locations = this.setUpProgram_(gl, context, size); + var locations = this.setUpProgram_(gl, context, size, pixelRatio); // set the "uniform" values var projectionMatrix = ol.transform.reset(this.projectionMatrix_); @@ -657,9 +657,10 @@ ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, image * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. * @return {ol.render.webgl.imagereplay.shader.Default.Locations} Locations. */ -ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context, size) { +ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context, size, pixelRatio) { // get the program var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment; var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex; @@ -1335,9 +1336,10 @@ ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. * @return {ol.render.webgl.linestringreplay.shader.Default.Locations} Locations. */ -ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, size) { +ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, size, pixelRatio) { // get the program var fragmentShader, vertexShader; fragmentShader = @@ -1384,6 +1386,7 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, gl.uniform1f(locations.u_miterLimit, this.state_.miterLimit); } gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_pixelRatio, pixelRatio); return locations; }; @@ -1398,7 +1401,9 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, */ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { //Save GL parameters. + /** @type {number} */ var tmpDepthFunc = gl.getParameter(gl.DEPTH_FUNC); + /** @type {boolean} */ var tmpDepthMask = gl.getParameter(gl.DEPTH_WRITEMASK); gl.enable(gl.DEPTH_TEST); diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/webgllinestringdefault.glsl index fbf287894b..c034a9662e 100644 --- a/src/ol/render/webgl/webgllinestringdefault.glsl +++ b/src/ol/render/webgl/webgllinestringdefault.glsl @@ -4,7 +4,7 @@ //! COMMON varying float v_round; -varying vec4 v_roundVertex; +varying vec2 v_roundVertex; varying float v_halfWidth; @@ -26,20 +26,15 @@ void main(void) { v_round = 0.0; float direction = a_direction / abs(a_direction); vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.); + v_roundVertex = projPos.xy; if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) { vec2 dirVect = a_nextPos - a_position; vec2 normal = normalize(vec2(-dirVect.y, dirVect.x)); offset = v_halfWidth * normal * direction; - if (mod(a_direction, 2.0) == 0.0) { - v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); - } } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) { vec2 dirVect = a_lastPos - a_position; vec2 normal = normalize(vec2(dirVect.y, -dirVect.x)); offset = v_halfWidth * normal * direction; - if (mod(a_direction, 2.0) == 0.0) { - v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); - } } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) { vec2 dirVect = a_nextPos - a_position; vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x)); @@ -50,7 +45,6 @@ void main(void) { offset = normal * direction * miterLength; if (mod(a_direction, 2.0) == 0.0) { v_round = 1.0; - v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } else if (miterLength > u_miterLimit) { offset = tmpNormal * direction * v_halfWidth; } @@ -106,7 +100,6 @@ void main(void) { offset = normal * length; if (mod(a_direction, 2.0) == 0.0) { v_round = 1.0; - v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.); } } if (!degenerate) { @@ -121,11 +114,13 @@ void main(void) { uniform float u_opacity; uniform vec4 u_color; uniform vec2 u_size; +uniform float u_pixelRatio; void main(void) { if (v_round > 0.0) { - vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x, (v_roundVertex.y + 1.0) / 2.0 * u_size.y); - if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth) { + vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio, + (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio); + if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) { discard; } } diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/webgllinestringdefaultshader.js index e69ebcb62b..d28e2f4967 100644 --- a/src/ol/render/webgl/webgllinestringdefaultshader.js +++ b/src/ol/render/webgl/webgllinestringdefaultshader.js @@ -23,14 +23,14 @@ goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultFragment) * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x, (v_roundVertex.y + 1.0) / 2.0 * u_size.y);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; +ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec4 b;varying float c;uniform float l;uniform vec4 m;uniform vec2 n;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*n.x,(b.y+1.0)/2.0*n.y);if(length(windowCoords-gl_FragCoord.xy)>c){discard;}} gl_FragColor=m;float alpha=m.a*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; +ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float l;uniform vec4 m;uniform vec2 n;uniform float o;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*n.x*o,(b.y+1.0)/2.0*n.y*o);if(length(windowCoords-gl_FragCoord.xy)>c*o){discard;}} gl_FragColor=m;float alpha=m.a*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; /** @@ -58,14 +58,14 @@ goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec4 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n if (mod(a_direction, 2.0) == 0.0) {\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n v_roundVertex = projPos + u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec4 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){bool degenerate=false;c=j/2.0;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;if(mod(g,2.0)==0.0){b=projPos+i*vec4(0.,0.,0.,0.);}} else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;if(mod(g,2.0)==0.0){b=projPos+i*vec4(0.,0.,0.,0.);}} else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;b=projPos+i*vec4(0.,0.,0.,0.);}else if(miterLength>k){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+i*vec4(longOffset,0.,0.);vec4 p2=projPos+i*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+i*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+i*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;b=projPos+i*vec4(0.,0.,0.,0.);}} if(!degenerate){vec4 offsets=i*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){bool degenerate=false;c=j/2.0;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>k){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+i*vec4(longOffset,0.,0.);vec4 p2=projPos+i*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+i*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+i*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=i*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; /** @@ -115,6 +115,12 @@ ol.render.webgl.linestringreplay.shader.Default.Locations = function(gl, program this.u_opacity = gl.getUniformLocation( program, goog.DEBUG ? 'u_opacity' : 'l'); + /** + * @type {WebGLUniformLocation} + */ + this.u_pixelRatio = gl.getUniformLocation( + program, goog.DEBUG ? 'u_pixelRatio' : 'o'); + /** * @type {WebGLUniformLocation} */ From 4d8777220a750f3057f0c778bc3171302180e159 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 27 Jun 2016 15:49:21 +0200 Subject: [PATCH 29/88] Fix compiler and compiled lib related problems. --- src/ol/render/webgl/imagereplay/index.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index daacc18459..30e2ef2941 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -230,7 +230,7 @@ ol.render.webgl.Replay.prototype.replay = function(context, // disable the vertex attrib arrays for (var i in locations) { - if (i.startsWith('a_')) { + if (typeof locations[i] === 'number') { gl.disableVertexAttribArray(locations[i]); } } @@ -1401,9 +1401,7 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, */ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { //Save GL parameters. - /** @type {number} */ var tmpDepthFunc = gl.getParameter(gl.DEPTH_FUNC); - /** @type {boolean} */ var tmpDepthMask = gl.getParameter(gl.DEPTH_WRITEMASK); gl.enable(gl.DEPTH_TEST); @@ -1418,8 +1416,12 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s gl.clear(gl.DEPTH_BUFFER_BIT); gl.disable(gl.DEPTH_TEST); //Restore GL parameters. - gl.depthMask(tmpDepthMask); - gl.depthFunc(tmpDepthFunc); + if (typeof tmpDepthMask === 'boolean') { + gl.depthMask(tmpDepthMask); + } + if (typeof tmpDepthFunc === 'number') { + gl.depthFunc(tmpDepthFunc); + } }; /** From 9d6a860cd1da80ea6c1538c7c7e7b509d19a4bf5 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 27 Jun 2016 18:48:28 +0200 Subject: [PATCH 30/88] Fix overdraw issue with Firefox As usual, Firefox is the good guy here, too, and this is an issue with Chrome --- src/ol/renderer/webgl/map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/renderer/webgl/map.js b/src/ol/renderer/webgl/map.js index 607d86600b..e055609ff6 100644 --- a/src/ol/renderer/webgl/map.js +++ b/src/ol/renderer/webgl/map.js @@ -77,7 +77,7 @@ ol.renderer.webgl.Map = function(container, map) { */ this.gl_ = ol.webgl.getContext(this.canvas_, { antialias: true, - depth: false, + depth: true, failIfMajorPerformanceCaveat: true, preserveDrawingBuffer: false, stencil: true From 3897312af635ddb9cd34918d40c932bb1d4d650a Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 29 Jun 2016 00:04:50 +0200 Subject: [PATCH 31/88] Add full support to different styles in one layer. --- src/ol/render/webgl/imagereplay/index.js | 90 ++++++++++++++++++------ 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 30e2ef2941..e6be33ce36 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -997,12 +997,24 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { ol.render.webgl.Replay.call(this, tolerance, maxExtent); -/** + /** * @private * @type {ol.render.webgl.linestringreplay.shader.Default.Locations} */ this.defaultLocations_ = null; + /** + * @private + * @type {Array.>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; + /** * @private * @type {{strokeColor: (Array.|null), @@ -1010,7 +1022,8 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { * lineDash: Array., * lineJoin: (string|undefined), * lineWidth: (number|undefined), - * miterLimit: (number|undefined)}|null} + * miterLimit: (number|undefined), + * changed: boolean}|null} */ this.state_ = { strokeColor: null, @@ -1018,7 +1031,8 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { lineDash: null, lineJoin: undefined, lineWidth: undefined, - miterLimit: undefined + miterLimit: undefined, + changed: false }; this.startIndices_ = [0]; @@ -1264,6 +1278,10 @@ ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringG var flatCoordinates = lineStringGeometry.getFlatCoordinates(); var stride = lineStringGeometry.getStride(); if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + if (this.state_.changed) { + this.styleIndices_.push(this.indices_.length - 1); + this.state_.changed = false; + } this.drawCoordinates_( flatCoordinates, 0, flatCoordinates.length, stride); this.startIndices_.push(this.indices_.length); @@ -1290,6 +1308,10 @@ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiL if (this.indices_.length > indexCount) { this.startIndices_.push(this.indices_.length); this.startIndicesFeature_.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(indexCount - 1); + this.state_.changed = false; + } } }; @@ -1378,13 +1400,6 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, false, 28, 24); // Enable renderer specific uniforms. If clauses needed, as otherwise the compiler complains. - gl.uniform4fv(locations.u_color, this.state_.strokeColor); - if (this.state_.lineWidth) { - gl.uniform1f(locations.u_lineWidth, this.state_.lineWidth); - } - if (this.state_.miterLimit) { - gl.uniform1f(locations.u_miterLimit, this.state_.miterLimit); - } gl.uniform2fv(locations.u_size, size); gl.uniform1f(locations.u_pixelRatio, pixelRatio); @@ -1410,12 +1425,28 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s if (!goog.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { - var end = this.startIndices_[this.startIndices_.length - 1]; + goog.asserts.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and style indices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, ii, end, nextStyle; + //Initial styling + nextStyle = this.styles_[0]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + for (i = 1, ii = this.styleIndices_.length; i < ii; ++i) { + end = this.styleIndices_[i]; + this.drawElements_(gl, context, 0, end); + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + } + end = this.startIndices_[this.startIndices_.length - 1]; this.drawElements_(gl, context, 0, end); } gl.clear(gl.DEPTH_BUFFER_BIT); gl.disable(gl.DEPTH_TEST); //Restore GL parameters. + //NOTE: Type checks will be obsolete when Closure Compiler recognizes the return type of specific + //gl.getParameter() calls. if (typeof tmpDepthMask === 'boolean') { gl.depthMask(tmpDepthMask); } @@ -1452,6 +1483,20 @@ ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplay_ = function(gl }; +/** + * @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 */ @@ -1459,10 +1504,6 @@ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillSty goog.asserts.assert(this.state_, 'this.state_ should not be null'); goog.asserts.assert(!fillStyle, 'fillStyle should be null'); goog.asserts.assert(strokeStyle, 'strokeStyle should not be null'); - var strokeStyleColor = strokeStyle.getColor(); - this.state_.strokeColor = ol.color.asArray(strokeStyleColor).map(function(c, i) { - return i != 3 ? c / 255 : c; - }) || ol.render.webgl.defaultStrokeStyle; var strokeStyleLineCap = strokeStyle.getLineCap(); this.state_.lineCap = strokeStyleLineCap !== undefined ? strokeStyleLineCap : ol.render.webgl.defaultLineCap; @@ -1472,12 +1513,19 @@ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillSty var strokeStyleLineJoin = strokeStyle.getLineJoin(); this.state_.lineJoin = strokeStyleLineJoin !== undefined ? strokeStyleLineJoin : ol.render.webgl.defaultLineJoin; - var strokeStyleWidth = strokeStyle.getWidth(); - this.state_.lineWidth = strokeStyleWidth !== undefined ? - strokeStyleWidth : ol.render.webgl.defaultLineWidth; - var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); - this.state_.miterLimit = strokeStyleMiterLimit !== undefined ? - strokeStyleMiterLimit : ol.render.webgl.defaultMiterLimit; + var strokeStyleColor = ol.color.asArray(strokeStyle.getColor()).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultStrokeStyle; + var strokeStyleWidth = strokeStyle.getWidth() || ol.render.webgl.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit() || 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]); + } }; From b7396b0b947681323c772c133afb3a0003f0ca32 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 29 Jun 2016 16:42:54 +0200 Subject: [PATCH 32/88] Improve code quality Cleaning up, making the code more consistent, and restructuring commonly used methods. --- src/ol/render/webgl/imagereplay/index.js | 168 ++++++++++++----------- src/ol/render/webgl/webgl.js | 15 ++ 2 files changed, 105 insertions(+), 78 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index e6be33ce36..11fbc4ee14 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -136,21 +136,6 @@ ol.render.webgl.Replay = function(tolerance, maxExtent) { goog.inherits(ol.render.webgl.Replay, ol.render.VectorContext); -/** - * @enum {number} - */ -ol.render.webgl.LineStringInstruction = { - 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 -}; - ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = goog.abstractMethod; ol.render.webgl.Replay.prototype.finish = goog.abstractMethod; @@ -159,7 +144,60 @@ ol.render.webgl.Replay.prototype.setUpProgram_ = goog.abstractMethod; ol.render.webgl.Replay.prototype.drawReplay_ = goog.abstractMethod; -ol.render.webgl.Replay.prototype.drawHitDetectionReplay_ = goog.abstractMethod; + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplay_ = function(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + if (!oneByOne) { + // draw all hit-detection features in "once" (by texture group) + return this.drawHitDetectionReplayAll_(gl, context, + skippedFeaturesHash, featureCallback); + } else { + // draw hit-detection features one by one + return this.drawHitDetectionReplayOneByOne_(gl, context, + skippedFeaturesHash, featureCallback, opt_hitExtent); + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplayAll_ = function(gl, context, skippedFeaturesHash, + featureCallback) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawReplay_(gl, context, skippedFeaturesHash, true); + + var result = featureCallback(null); + if (result) { + return result; + } else { + return undefined; + } +}; + + +ol.render.webgl.Replay.prototype.drawHitDetectionReplayOneByOne_ = goog.abstractMethod; + /** * @param {ol.webgl.Context} context Context. @@ -809,7 +847,6 @@ ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, 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. @@ -864,8 +901,8 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayAll_ = function(gl, * @return {T|undefined} Callback result. * @template T */ -ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, featureCallback, - opt_hitExtent) { +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { ol.DEBUG && console.assert(this.hitDetectionTextures_.length === this.hitDetectionGroupIndices_.length, 'number of hitDetectionTextures and hitDetectionGroupIndices match'); @@ -1055,7 +1092,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord var numVertices = this.vertices_.length; var numIndices = this.indices_.length; //To save a vertex, the direction of a point is a product of the sign (1 or -1), a prime from - //ol.render.webgl.LineStringInstruction, and a rounding factor (1 or 2). If the product is even, + //ol.render.webgl.lineStringInstruction, and a rounding factor (1 or 2). If the product is even, //we round it. If it is odd, we don't. var lineJoin = this.state_.lineJoin === 'bevel' ? 0 : this.state_.lineJoin === 'miter' ? 1 : 2; @@ -1092,10 +1129,10 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord if (lineCap) { numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); this.indices_[numIndices++] = n + 2; this.indices_[numIndices++] = n; @@ -1108,10 +1145,10 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord } numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.LineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); lastIndex = n + 3; @@ -1128,10 +1165,10 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord p0 = p0 || [0, 0]; numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); + lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.LineStringInstruction.END_LINE * (lineCap || 1), numVertices); + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); this.indices_[numIndices++] = n; this.indices_[numIndices++] = lastIndex - 1; @@ -1143,10 +1180,10 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord if (lineCap) { numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); + lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.LineStringInstruction.END_LINE_CAP * lineCap, numVertices); + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); this.indices_[numIndices++] = n + 2; this.indices_[numIndices++] = n; @@ -1168,13 +1205,13 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord ? 1 : -1; numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); + sign * ol.render.webgl.lineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.LineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); if (i > offset) { this.indices_[numIndices++] = n; @@ -1196,7 +1233,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord //Add miter if (lineJoin) { numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.MITER_TOP * lineJoin, numVertices); + sign * ol.render.webgl.lineStringInstruction.MITER_TOP * lineJoin, numVertices); this.indices_[numIndices++] = n + 1; this.indices_[numIndices++] = n + 3; @@ -1212,10 +1249,10 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord ? 1 : -1; numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.LineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); this.indices_[numIndices++] = n; this.indices_[numIndices++] = lastIndex - 1; @@ -1416,12 +1453,20 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, */ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { //Save GL parameters. - var tmpDepthFunc = gl.getParameter(gl.DEPTH_FUNC); - var tmpDepthMask = gl.getParameter(gl.DEPTH_WRITEMASK); + 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); + } + + var nextStyle; + //Initial styling + nextStyle = this.styles_[0]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - gl.enable(gl.DEPTH_TEST); - gl.depthMask(true); - gl.depthFunc(gl.NOTEQUAL); if (!goog.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { @@ -1429,10 +1474,8 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s 'number of styles and style indices match'); //Draw by style groups to minimize drawElements() calls. - var i, ii, end, nextStyle; - //Initial styling - nextStyle = this.styles_[0]; - this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + var i, ii, end; + for (i = 1, ii = this.styleIndices_.length; i < ii; ++i) { end = this.styleIndices_[i]; this.drawElements_(gl, context, 0, end); @@ -1442,46 +1485,15 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s end = this.startIndices_[this.startIndices_.length - 1]; this.drawElements_(gl, context, 0, end); } - gl.clear(gl.DEPTH_BUFFER_BIT); - gl.disable(gl.DEPTH_TEST); - //Restore GL parameters. - //NOTE: Type checks will be obsolete when Closure Compiler recognizes the return type of specific - //gl.getParameter() calls. - if (typeof tmpDepthMask === 'boolean') { + if (!hitDetection) { + gl.clear(gl.DEPTH_BUFFER_BIT); + gl.disable(gl.DEPTH_TEST); + //Restore GL parameters. gl.depthMask(tmpDepthMask); - } - if (typeof tmpDepthFunc === 'number') { gl.depthFunc(tmpDepthFunc); } }; -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {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.LineStringReplay.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); - //} - return 0; -}; - /** * @private diff --git a/src/ol/render/webgl/webgl.js b/src/ol/render/webgl/webgl.js index 262a7639d1..498ff2d72f 100644 --- a/src/ol/render/webgl/webgl.js +++ b/src/ol/render/webgl/webgl.js @@ -44,3 +44,18 @@ ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0]; * @type {number} */ ol.render.webgl.defaultLineWidth = 1; + +/** + * @enum {number} + */ +ol.render.webgl.lineStringInstruction = { + 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 +}; From 241505050005614571c109f8f00d841d0b168403 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 1 Jul 2016 11:39:41 +0200 Subject: [PATCH 33/88] Add hit detection support to LineStringReplay Also, making it more consistent with ImageReplay --- src/ol/render/webgl/imagereplay/index.js | 95 +++++++++++++++++++----- 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 11fbc4ee14..a80fd8b785 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1072,7 +1072,7 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { changed: false }; - this.startIndices_ = [0]; + this.startIndices_ = []; }; ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); @@ -1316,13 +1316,13 @@ ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringG var stride = lineStringGeometry.getStride(); if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { if (this.state_.changed) { - this.styleIndices_.push(this.indices_.length - 1); + this.styleIndices_.push(this.indices_.length); this.state_.changed = false; } - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); this.startIndices_.push(this.indices_.length); this.startIndicesFeature_.push(feature); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); } }; @@ -1343,10 +1343,10 @@ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiL } } if (this.indices_.length > indexCount) { - this.startIndices_.push(this.indices_.length); + this.startIndices_.push(indexCount); this.startIndicesFeature_.push(feature); if (this.state_.changed) { - this.styleIndices_.push(indexCount - 1); + this.styleIndices_.push(indexCount); this.state_.changed = false; } } @@ -1365,6 +1365,13 @@ ol.render.webgl.LineStringReplay.prototype.finish = function(context) { this.indicesBuffer_ = new ol.webgl.Buffer(this.indices_); context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + this.startIndices_.push(this.indices_.length); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_.pop(); + } + this.vertices_ = null; this.indices_ = null; }; @@ -1462,28 +1469,22 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s gl.depthFunc(gl.NOTEQUAL); } - var nextStyle; - //Initial styling - nextStyle = this.styles_[0]; - this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - if (!goog.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { goog.asserts.assert(this.styles_.length === this.styleIndices_.length, - 'number of styles and style indices match'); + 'number of styles and styleIndices match'); //Draw by style groups to minimize drawElements() calls. - var i, ii, end; - - for (i = 1, ii = this.styleIndices_.length; i < ii; ++i) { - end = this.styleIndices_[i]; - this.drawElements_(gl, context, 0, end); + var i, start, end, nextStyle; + end = this.startIndices_[this.startIndices_.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; nextStyle = this.styles_[i]; this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + this.drawElements_(gl, context, start, end); + end = start; } - end = this.startIndices_[this.startIndices_.length - 1]; - this.drawElements_(gl, context, 0, end); } if (!hitDetection) { gl.clear(gl.DEPTH_BUFFER_BIT); @@ -1495,6 +1496,62 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s }; +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + goog.asserts.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices_.length - 2; + end = this.startIndices_[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices_[featureIndex] >= groupStart) { + start = this.startIndices_[featureIndex]; + feature = this.startIndicesFeature_[featureIndex]; + featureUid = goog.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements_(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + /** * @private * @param {WebGLRenderingContext} gl gl. From c966e21e40cefca1a71f0a7ee4fcb05d3409b4d9 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 1 Jul 2016 14:10:09 +0200 Subject: [PATCH 34/88] Fix black canvas when image is missing --- src/ol/renderer/webgl/imagelayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/renderer/webgl/imagelayer.js b/src/ol/renderer/webgl/imagelayer.js index f85bbd4917..8e7507ef9d 100644 --- a/src/ol/renderer/webgl/imagelayer.js +++ b/src/ol/renderer/webgl/imagelayer.js @@ -171,7 +171,7 @@ ol.renderer.webgl.ImageLayer.prototype.prepareFrame = function(frameState, layer this.updateLogos(frameState, imageSource); } - return true; + return !!image; }; From 16dfc5dfba5b64234181ea723bc2e3f4cc36425f Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Sun, 3 Jul 2016 17:44:24 +0200 Subject: [PATCH 35/88] Adjust replays to new version. --- src/ol/render/webgl/imagereplay/index.js | 53 ++++++++++++------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index a80fd8b785..a85b5c77f8 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -71,19 +71,19 @@ ol.render.webgl.Replay = function(tolerance, maxExtent) { this.origin_ = ol.extent.getCenter(maxExtent); /** - * @type {!goog.vec.Mat4.Number} + * @type {ol.Transform} * @private */ this.projectionMatrix_ = ol.transform.create(); /** - * @type {!goog.vec.Mat4.Number} + * @type {ol.Transform} * @private */ this.offsetRotateMatrix_ = ol.transform.create(); /** - * @type {!goog.vec.Mat4.Number} + * @type {ol.Transform} * @private */ this.offsetScaleMatrix_ = ol.transform.create(); @@ -133,7 +133,7 @@ ol.render.webgl.Replay = function(tolerance, maxExtent) { this.verticesBuffer_ = null; }; -goog.inherits(ol.render.webgl.Replay, ol.render.VectorContext); +ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext); ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = goog.abstractMethod; @@ -225,12 +225,12 @@ ol.render.webgl.Replay.prototype.replay = function(context, // bind the vertices buffer goog.asserts.assert(this.verticesBuffer_, 'verticesBuffer must not be null'); - context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); // bind the indices buffer goog.asserts.assert(this.indicesBuffer_, 'indicesBuffer must not be null'); - context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); var locations = this.setUpProgram_(gl, context, size, pixelRatio); @@ -1359,11 +1359,11 @@ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiL ol.render.webgl.LineStringReplay.prototype.finish = function(context) { // create, bind, and populate the vertices buffer this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); - context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); // create, bind, and populate the indices buffer this.indicesBuffer_ = new ol.webgl.Buffer(this.indices_); - context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); this.startIndices_.push(this.indices_.length); @@ -1386,8 +1386,7 @@ ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function // be used by other LineStringReplay instances (for other layers). And // they will be deleted when disposing of the ol.webgl.Context // object. - goog.asserts.assert(!goog.isNull(this.verticesBuffer_), - 'verticesBuffer must not be null'); + goog.asserts.assert(this.verticesBuffer_, 'verticesBuffer must not be null'); var verticesBuffer = this.verticesBuffer_; var indicesBuffer = this.indicesBuffer_; return function() { @@ -1416,7 +1415,7 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, // get the locations var locations; - if (goog.isNull(this.defaultLocations_)) { + if (!this.defaultLocations_) { locations = new ol.render.webgl.linestringreplay.shader.Default .Locations(gl, program); this.defaultLocations_ = locations; @@ -1428,19 +1427,19 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, // enable the vertex attrib arrays gl.enableVertexAttribArray(locations.a_lastPos); - gl.vertexAttribPointer(locations.a_lastPos, 2, goog.webgl.FLOAT, + gl.vertexAttribPointer(locations.a_lastPos, 2, ol.webgl.FLOAT, false, 28, 0); gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, false, 28, 8); gl.enableVertexAttribArray(locations.a_nextPos); - gl.vertexAttribPointer(locations.a_nextPos, 2, goog.webgl.FLOAT, + gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT, false, 28, 16); gl.enableVertexAttribArray(locations.a_direction); - gl.vertexAttribPointer(locations.a_direction, 1, goog.webgl.FLOAT, + gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT, false, 28, 24); // Enable renderer specific uniforms. If clauses needed, as otherwise the compiler complains. @@ -1469,7 +1468,7 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s gl.depthFunc(gl.NOTEQUAL); } - if (!goog.object.isEmpty(skippedFeaturesHash)) { + if (!ol.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { goog.asserts.assert(this.styles_.length === this.styleIndices_.length, @@ -1654,10 +1653,10 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { this.defaultLocations_ = null; /** - * @type {!goog.vec.Mat4.Number} + * @type {ol.Transform} * @private */ - this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity(); + this.projectionMatrix_ = ol.transform.create(); /** * @type {Array.} @@ -1765,7 +1764,7 @@ ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, ol.render.webgl.PolygonReplay.prototype.finish = function(context) { // create, bind, and populate the vertices buffer this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); - context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); var indices = this.indices_; var bits = context.hasOESElementIndexUint ? 32 : 16; @@ -1775,7 +1774,7 @@ ol.render.webgl.PolygonReplay.prototype.finish = function(context) { // create, bind, and populate the indices buffer this.indicesBuffer_ = new ol.webgl.Buffer(indices); - context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); this.lineStringReplay_.finish(context); }; @@ -1833,12 +1832,12 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(co // bind the vertices buffer goog.asserts.assert(!goog.isNull(this.verticesBuffer_), 'verticesBuffer must not be null'); - context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); // bind the indices buffer goog.asserts.assert(!goog.isNull(this.indicesBuffer_), 'indicesBuffer must not be null'); - context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); // get the program var fragmentShader, vertexShader; @@ -1862,11 +1861,11 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(co // enable the vertex attrib arrays gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, false, 24, 0); gl.enableVertexAttribArray(locations.a_color); - gl.vertexAttribPointer(locations.a_color, 4, goog.webgl.FLOAT, + gl.vertexAttribPointer(locations.a_color, 4, ol.webgl.FLOAT, false, 24, 8); // set the "uniform" values @@ -1910,14 +1909,14 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(co */ ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { var elementType = context.hasOESElementIndexUint ? - goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; + ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; // var elementSize = context.hasOESElementIndexUint ? 4 : 2; - if (!goog.object.isEmpty(skippedFeaturesHash)) { + if (!ol.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { var numItems = this.indices_.length; - gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, 0); + gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, 0); } }; From 48d3bfe29790e74d932ca672a68df5aaf1fc9661 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 4 Jul 2016 13:31:38 +0200 Subject: [PATCH 36/88] Take line width into account when forming miters. --- src/ol/render/webgl/webgllinestringdefault.glsl | 3 ++- src/ol/render/webgl/webgllinestringdefaultshader.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/webgllinestringdefault.glsl index c034a9662e..72edd05366 100644 --- a/src/ol/render/webgl/webgllinestringdefault.glsl +++ b/src/ol/render/webgl/webgllinestringdefault.glsl @@ -22,6 +22,7 @@ uniform float u_miterLimit; void main(void) { bool degenerate = false; v_halfWidth = u_lineWidth / 2.0; + float miterLimit = u_miterLimit + u_lineWidth; vec2 offset; v_round = 0.0; float direction = a_direction / abs(a_direction); @@ -45,7 +46,7 @@ void main(void) { offset = normal * direction * miterLength; if (mod(a_direction, 2.0) == 0.0) { v_round = 1.0; - } else if (miterLength > u_miterLimit) { + } else if (miterLength > miterLimit) { offset = tmpNormal * direction * v_halfWidth; } } else { diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/webgllinestringdefaultshader.js index d28e2f4967..bec5b5baaf 100644 --- a/src/ol/render/webgl/webgllinestringdefaultshader.js +++ b/src/ol/render/webgl/webgllinestringdefaultshader.js @@ -58,14 +58,14 @@ goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > u_miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n float miterLimit = u_miterLimit + u_lineWidth;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){bool degenerate=false;c=j/2.0;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>k){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+i*vec4(longOffset,0.,0.);vec4 p2=projPos+i*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+i*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+i*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=i*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; +ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){bool degenerate=false;c=j/2.0;float miterLimit=k+j;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+i*vec4(longOffset,0.,0.);vec4 p2=projPos+i*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+i*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+i*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=i*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; /** From 1c7203a20357819116332ca5177a9c3ed00bd45e Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 4 Jul 2016 16:19:48 +0200 Subject: [PATCH 37/88] Add skipping capability to LineStringReplay --- src/ol/render/webgl/imagereplay/index.js | 42 +++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index a85b5c77f8..4bf09b3ce6 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1469,7 +1469,7 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s } if (!ol.object.isEmpty(skippedFeaturesHash)) { - // TODO: draw by blocks to skip features + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); } else { goog.asserts.assert(this.styles_.length === this.styleIndices_.length, 'number of styles and styleIndices match'); @@ -1495,6 +1495,46 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, s }; +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices_.length - 2; + end = start = this.startIndices_[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices_[featureIndex] >= groupStart) { + featureStart = this.startIndices_[featureIndex]; + feature = this.startIndicesFeature_[featureIndex]; + featureUid = goog.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements_(gl, context, start, end); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements_(gl, context, start, end); + } + } +}; + + /** * @private * @param {WebGLRenderingContext} gl gl. From f96a4a73019f8a5101d083501e3060111c3a708f Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 4 Jul 2016 16:47:30 +0200 Subject: [PATCH 38/88] Properly handle zeros in stroke style. --- src/ol/render/webgl/imagereplay/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 4bf09b3ce6..ab923ce32a 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1624,8 +1624,12 @@ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillSty var strokeStyleColor = ol.color.asArray(strokeStyle.getColor()).map(function(c, i) { return i != 3 ? c / 255 : c; }) || ol.render.webgl.defaultStrokeStyle; - var strokeStyleWidth = strokeStyle.getWidth() || ol.render.webgl.defaultLineWidth; - var strokeStyleMiterLimit = strokeStyle.getMiterLimit() || ol.render.webgl.defaultMiterLimit; + 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; From 638b7752fa9337cebfd64030f42830cd77f23d3d Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 5 Jul 2016 17:39:28 +0200 Subject: [PATCH 39/88] Fix bugs in LineStringReplay. --- src/ol/render/webgl/imagereplay/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index ab923ce32a..0364d09f06 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1150,7 +1150,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord numVertices = this.addVertices_([0, 0], p1, p2, -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - lastIndex = n + 3; + lastIndex = numVertices / 7 - 1; continue; } From e893603966b2a73ddeba5f30261a0474ffee8594 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 5 Jul 2016 18:19:24 +0200 Subject: [PATCH 40/88] Add tests to LineStringReplay --- test/spec/ol/render/webgl/replay.test.js | 205 ++++++++++++++++++ .../expected/linestring-strokes-webgl.png | Bin 0 -> 549 bytes .../spec/ol/style/linestring.test.js | 7 + 3 files changed, 212 insertions(+) create mode 100644 test_rendering/spec/ol/style/expected/linestring-strokes-webgl.png diff --git a/test/spec/ol/render/webgl/replay.test.js b/test/spec/ol/render/webgl/replay.test.js index ed5d61f50d..7e157b5ade 100644 --- a/test/spec/ol/render/webgl/replay.test.js +++ b/test/spec/ol/render/webgl/replay.test.js @@ -1,6 +1,8 @@ goog.provide('ol.test.render.webgl.Replay'); goog.require('ol.extent'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); goog.require('ol.geom.MultiPoint'); goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Point'); @@ -175,6 +177,209 @@ describe('ol.render.webgl.ImageReplay', function() { }); }); +describe('ol.render.webgl.LineStringReplay', function() { + var replay; + + var strokeStyle1 = new ol.style.Stroke({ + color: [0, 255, 0, 0.4] + }); + + var strokeStyle2 = new ol.style.Stroke({ + color: [255, 0, 0, 1], + lineCap: 'square', + lineJoin: 'miter' + }); + + beforeEach(function() { + var tolerance = 0.1; + var maxExtent = [-10000, -20000, 10000, 20000]; + replay = new ol.render.webgl.LineStringReplay(tolerance, maxExtent); + }); + + describe('#setFillStrokeStyle', function() { + + it('set expected states', function() { + replay.setFillStrokeStyle(null, strokeStyle1); + expect(replay.state_).not.be(null); + expect(replay.state_.lineCap).to.be('round'); + expect(replay.state_.lineJoin).to.be('round'); + expect(replay.state_.strokeColor).to.eql([0, 1, 0, 0.4]); + expect(replay.state_.lineWidth).to.be(1); + expect(replay.state_.miterLimit).to.be(10); + expect(replay.state_.changed).to.be(true); + expect(replay.styles_).to.have.length(1); + + replay.setFillStrokeStyle(null, strokeStyle2); + expect(replay.state_.lineCap).to.be('square'); + expect(replay.state_.lineJoin).to.be('miter'); + expect(replay.state_.strokeColor).to.eql([1, 0, 0, 1]); + expect(replay.state_.lineWidth).to.be(1); + expect(replay.state_.miterLimit).to.be(10); + expect(replay.state_.changed).to.be(true); + expect(replay.styles_).to.have.length(2); + + }); + }); + + describe('#drawLineString', function() { + + it('sets the buffer data', function() { + var linestring; + + linestring = new ol.geom.LineString( + [[1000, 2000], [2000, 3000]]); + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(linestring, null); + expect(replay.vertices_).to.have.length(56); + expect(replay.indices_).to.have.length(18); + expect(replay.state_.changed).to.be(false); + expect(replay.startIndices_).to.have.length(1); + expect(replay.startIndicesFeature_).to.have.length(1); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000]]); + replay.drawLineString(linestring, null); + expect(replay.vertices_).to.have.length(140); + expect(replay.indices_).to.have.length(48); + expect(replay.state_.changed).to.be(false); + expect(replay.startIndices_).to.have.length(2); + expect(replay.startIndicesFeature_).to.have.length(2); + }); + }); + + describe('#drawMultiLineString', function() { + + it('sets the buffer data', function() { + var multilinestring; + + multilinestring = new ol.geom.MultiLineString( + [[[1000, 2000], [2000, 3000]], + [[1000, 3000], [2000, 4000], [3000, 3000]]]); + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawMultiLineString(multilinestring, null); + expect(replay.vertices_).to.have.length(140); + expect(replay.indices_).to.have.length(48); + expect(replay.state_.changed).to.be(false); + expect(replay.startIndices_).to.have.length(1); + expect(replay.startIndicesFeature_).to.have.length(1); + }); + }); + + describe('#drawCoordinates_', function() { + + it('triangulates linestrings', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1], + lineCap: 'butt', + lineJoin: 'bevel' + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices_).to.eql( + [2, 0, 1, 4, 2, 1, + 2, 4, 3, + 5, 3, 4, 4, 6, 5]); + }); + + it('optionally creates miters', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1], + lineCap: 'butt' + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices_).to.eql( + [2, 0, 1, 4, 2, 1, + 2, 4, 3, 3, 5, 2, + 6, 3, 4, 4, 7, 6]); + }); + + it('optionally creates caps', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1] + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices_).to.eql( + [2, 0, 1, 1, 3, 2, + 4, 2, 3, 6, 4, 3, + 4, 6, 5, 5, 7, 4, + 8, 5, 6, 6, 9, 8, + 10, 8, 9, 9, 11, 10]); + }); + + it('respects segment orientation', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1], + lineCap: 'butt', + lineJoin: 'bevel' + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 2000], [3000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices_).to.eql( + [2, 0, 1, 4, 2, 0, + 2, 4, 3, + 5, 3, 4, 4, 6, 5]); + }); + + it('closes boundaries', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1], + lineCap: 'butt', + lineJoin: 'bevel' + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000], [1000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices_).to.eql( + [0, 2, 1, 3, 1, 2, + 5, 3, 2, + 3, 5, 4, 6, 4, 5, + 8, 6, 5, + 6, 8, 7, 9, 7, 8, + 10, 9, 8]); + expect(replay.vertices_.slice(0, 7)).to.eql( + replay.vertices_.slice(-14, -7)); + expect(replay.vertices_.slice(14, 21)).to.eql( + replay.vertices_.slice(-7)); + }); + }); +}); + describe('ol.render.webgl.PolygonReplay', function() { var replay; diff --git a/test_rendering/spec/ol/style/expected/linestring-strokes-webgl.png b/test_rendering/spec/ol/style/expected/linestring-strokes-webgl.png new file mode 100644 index 0000000000000000000000000000000000000000..95e1a9a602a3f7cb3ea0f9bd4cfc26f1bf1bec2a GIT binary patch literal 549 zcmV+=0^0qFP)v*F;Ha}d|E1!RCI8}8F8G6Gm

8O%(R6dLPY zJy{-)LM6|r5{^(e$n(YYzAsK)zpYqW_8fV+oPPBM0(D%yuE@@*7X+$MMQin~t+;x~ z-s3KG?6@SzV)4$J6dvN|^K+J#x2FeO2j%M*`cvvSO4pFeT`8-!IV=OB?IsO`^hcIP6SrmOoC#k5pTiw%xA6`Gx9 zaD*t;O$gyGgaE%T#VU~`hCtpDd3I`9BEe(h#8dMN3E}@OyWuketwCalhXw$@r~w3k ziaeGeaBqNp0b&sXL_@0qAUwrjtUxHS2njG&InDP71%U~%?TWx?s}oNsXea7g8s9** zxy>AjAtX}?VI=R610uI7xezD_VK^?55V^hDhKMH=D`^vCI+mr5SV6SMvQ)=j!K4eM z4m^nq?O3z1ECI2GFdfTWkVB`o#*O%0^;qVBa1T5!lxi$fAYKsavD^hBJ+-xts6H?c zfoKmr4OH9(RLA`0Nd+DgB72gxK`akEjVti3Xp*HIfd$%^Bdw_1_t{#y_DHd7sgMe~ ngw(>`Ov2>u;RA?!$%%yDlyHeN8g_al00000NkvXXu0mjf4&Uvu literal 0 HcmV?d00001 diff --git a/test_rendering/spec/ol/style/linestring.test.js b/test_rendering/spec/ol/style/linestring.test.js index b3e14dbbdc..25f5fed7fc 100644 --- a/test_rendering/spec/ol/style/linestring.test.js +++ b/test_rendering/spec/ol/style/linestring.test.js @@ -98,5 +98,12 @@ describe('ol.rendering.style.LineString', function() { map, 'spec/ol/style/expected/linestring-strokes-canvas.png', 3.0, done); }); + it('tests the WebGL renderer', function(done) { + assertWebGL(); + map = createMap('webgl'); + createFeatures(); + expectResemble(map, 'spec/ol/style/expected/linestring-strokes-webgl.png', + 3.0, done); + }); }); }); From 269c3eb943d87076f693e8fa9fc84f6c2e696db1 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 6 Jul 2016 10:43:49 +0200 Subject: [PATCH 41/88] Set WebGL LineString threshold to 14.6 This commit will be a fixup for the previous one, or get deleted eventually. --- test_rendering/spec/ol/style/linestring.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_rendering/spec/ol/style/linestring.test.js b/test_rendering/spec/ol/style/linestring.test.js index 25f5fed7fc..05a6e10a0d 100644 --- a/test_rendering/spec/ol/style/linestring.test.js +++ b/test_rendering/spec/ol/style/linestring.test.js @@ -103,7 +103,7 @@ describe('ol.rendering.style.LineString', function() { map = createMap('webgl'); createFeatures(); expectResemble(map, 'spec/ol/style/expected/linestring-strokes-webgl.png', - 3.0, done); + 14.6, done); }); }); }); From 18cf6c30bd00eb552562ae54b91efc47d6484c05 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Thu, 7 Jul 2016 16:07:27 +0200 Subject: [PATCH 42/88] Add basic polygon renderer. --- package.json | 2 +- src/ol/render/webgl/imagereplay/index.js | 350 ++++++++---------- src/ol/render/webgl/webglpolygondefault.glsl | 11 +- .../render/webgl/webglpolygondefaultshader.js | 30 +- 4 files changed, 175 insertions(+), 218 deletions(-) diff --git a/package.json b/package.json index 73eaacd414..fd446ace18 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "browserify": "13.1.1", "closure-util": "1.15.1", "derequire": "2.0.3", - "earcut": "^1.4.2", + "earcut": "2.1.1", "fs-extra": "1.0.0", "glob": "7.1.1", "handlebars": "4.0.6", diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 0364d09f06..249d7e4408 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -23,8 +23,8 @@ goog.require('ol.render.webgl.linestringreplay.shader.DefaultFragment'); goog.require('ol.render.webgl.linestringreplay.shader.DefaultVertex'); goog.require('ol.render.webgl.polygonreplay.shader.Default'); goog.require('ol.render.webgl.polygonreplay.shader.Default.Locations'); -//goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); -//goog.require('ol.render.webgl.polygonreplay.shader.DefaultVertex'); +goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); +goog.require('ol.render.webgl.polygonreplay.shader.DefaultVertex'); goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); @@ -132,6 +132,13 @@ ol.render.webgl.Replay = function(tolerance, maxExtent) { */ this.verticesBuffer_ = null; + /** + * Optional parameter for PolygonReplay instances. + * @type {ol.render.webgl.LineStringReplay|undefined} + * @private + */ + this.lineStringReplay_ = undefined; + }; ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext); @@ -273,6 +280,12 @@ ol.render.webgl.Replay.prototype.replay = function(context, } } + if (this.lineStringReplay_) { + this.lineStringReplay_.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } return result; }; @@ -1072,8 +1085,6 @@ ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { changed: false }; - this.startIndices_ = []; - }; ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); @@ -1652,44 +1663,9 @@ ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillSty ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { ol.render.webgl.Replay.call(this, tolerance, maxExtent); - /** - * @private - * @type {ol.Color|undefined} - */ - this.fillColor_ = undefined; - - /** - * @private - * @type {ol.Color|undefined} - */ - this.strokeColor_ = undefined; - - /** - * @private - */ this.lineStringReplay_ = new ol.render.webgl.LineStringReplay( tolerance, maxExtent); - /** - * The origin of the coordinate system for the point coordinates sent to - * the GPU. - * @private - * @type {ol.Coordinate} - */ - this.origin_ = ol.extent.getCenter(maxExtent); - - /** - * @type {Array.} - * @private - */ - this.indices_ = []; - - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.indicesBuffer_ = null; - /** * @private * @type {ol.render.webgl.polygonreplay.shader.Default.Locations} @@ -1697,67 +1673,55 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { this.defaultLocations_ = null; /** - * @type {ol.Transform} * @private + * @type {Array.>} */ - this.projectionMatrix_ = ol.transform.create(); + this.styles_ = []; /** + * @private * @type {Array.} - * @private */ - this.vertices_ = []; + this.styleIndices_ = []; /** - * @type {ol.webgl.Buffer} * @private + * @type {{fillColor: (Array.|null), + * changed: boolean}|null} */ - this.verticesBuffer_ = null; + this.state_ = { + fillColor: null, + changed: false + }; - /** - * Start index per feature (the index). - * @type {Array.} - * @private - */ - this.startIndices_ = []; - - /** - * Start index per feature (the feature). - * @type {Array.|Array.} - * @private - */ - this.startIndicesFeature_ = []; }; ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); /** * Draw one polygon. - * @param {Array.>} coordinates Coordinates. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {Array.|null} holeIndices Hole indices. + * @param {number} stride Stride. * @private */ -ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(coordinates) { - // Triangulate the polgon - var triangulation = ol.ext.earcut(coordinates, true); - var i, ii; - var indices = triangulation.indices; +ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(flatCoordinates, holeIndices, stride) { + // Triangulate the polygon + var nextIndex = this.vertices_.length / 2 + 1; + flatCoordinates = flatCoordinates.map(function(curr, index) { + if (index % 2 === 0) { + return curr - this.origin_[0]; + } else { + return curr - this.origin_[1]; + } + }, this); + var triangulation = ol.ext.earcut(flatCoordinates, holeIndices, stride).map(function(curr) { + return curr + nextIndex; + }); - // Shift the indices to take into account previously handled polygons - var offset = this.vertices_.length / 6; - for (i = 0, ii = indices.length; i < ii; ++i) { - this.indices_.push(indices[i] + offset); - } - - // Add the color property to each vertex - // TODO performance: make it more efficient - var vertices = triangulation.vertices; - for (i = 0, ii = vertices.length / 2; i < ii; ++i) { - this.vertices_.push(vertices[2 * i]); - this.vertices_.push(vertices[2 * i + 1]); - this.vertices_.push(this.fillColor_[0]); - this.vertices_.push(this.fillColor_[1]); - this.vertices_.push(this.fillColor_[2]); - this.vertices_.push(this.fillColor_[3]); + if (triangulation.length > 2) { + ol.array.extend(this.vertices_, flatCoordinates); + ol.array.extend(this.indices_, triangulation); } }; @@ -1765,16 +1729,44 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(coordinates) /** * @inheritDoc */ -ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(geometry, feature) { - if (goog.isNull(this.fillColor_)) { - return; +ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { + var polygons = multiPolygonGeometry.getPolygons(); + var stride = multiPolygonGeometry.getStride(); + var currIndex = this.indices_.length; + var currLineIndex = this.lineStringReplay_.indices_.length; + var i, ii, j, jj; + for (i = 0, ii = polygons.length; i < ii; ++i) { + var linearRings = polygons[i].getLinearRings(); + if (linearRings.length > 0) { + var flatCoordinates = linearRings[0].getFlatCoordinates(); + this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); + var holeIndices = []; + var holeFlatCoords; + for (j = 1, jj = linearRings.length; j < jj; ++j) { + holeIndices.push(flatCoordinates.length / 2); + holeFlatCoords = linearRings[i].getFlatCoordinates(); + ol.array.extend(flatCoordinates, holeFlatCoords); + this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); + } + holeIndices = holeIndices.length === 0 ? null : holeIndices; + this.drawCoordinates_(flatCoordinates, holeIndices, stride); + } } - var coordinatess = geometry.getCoordinates(); - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); - var i, ii; - for (i = 0, ii = coordinatess.length; i < ii; i++) { - this.drawCoordinates_(coordinatess[i]); + if (this.indices_.length > currIndex) { + this.startIndices_.push(currIndex); + this.startIndicesFeature_.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(currIndex); + this.state_.changed = false; + } + } + if (this.lineStringReplay_.indices_.length > currLineIndex) { + this.lineStringReplay_.startIndices_.push(currLineIndex); + this.lineStringReplay_.startIndicesFeature_.push(feature); + if (this.lineStringReplay_.state_.changed) { + this.lineStringReplay_.styleIndices_.push(currLineIndex); + this.lineStringReplay_.state_.changed = false; + } } }; @@ -1783,21 +1775,35 @@ ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(geometry, fe * @inheritDoc */ ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { - if (goog.isNull(this.fillColor_)) { - return; - } - var coordinates = polygonGeometry.getCoordinates(); - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); - this.drawCoordinates_(coordinates); - - if (!goog.isNull(this.lineStringReplay_.state_.strokeColor)) { - var linearRings = polygonGeometry.getLinearRings(); - var i, ii; - for (i = 0, ii = linearRings.length; i < ii; i++) { - //FIXME: Substitute zeros with appropriate values when implementing. - this.lineStringReplay_.drawCoordinates_(linearRings[i].getFlatCoordinates(), 0, 0, 0); + var linearRings = polygonGeometry.getLinearRings(); + var stride = polygonGeometry.getStride(); + if (linearRings.length > 0) { + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(this.indices_.length); + this.state_.changed = false; } + + this.lineStringReplay_.startIndices_.push(this.lineStringReplay_.indices_.length); + this.lineStringReplay_.startIndicesFeature_.push(feature); + if (this.lineStringReplay_.state_.changed) { + this.lineStringReplay_.styleIndices_.push(this.lineStringReplay_.indices_.length); + this.lineStringReplay_.state_.changed = false; + } + + var flatCoordinates = linearRings[0].getFlatCoordinates(); + this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); + var holeIndices = []; + var i, ii, holeFlatCoords; + for (i = 1, ii = linearRings.length; i < ii; ++i) { + holeIndices.push(flatCoordinates.length / 2); + holeFlatCoords = linearRings[i].getFlatCoordinates(); + ol.array.extend(flatCoordinates, holeFlatCoords); + this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); + } + holeIndices = holeIndices.length === 0 ? null : holeIndices; + this.drawCoordinates_(flatCoordinates, holeIndices, stride); } }; @@ -1848,41 +1854,14 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(co /** + * @private + * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. * @param {ol.Size} size Size. * @param {number} pixelRatio Pixel ratio. - * @param {number} opacity Global opacity. - * @param {number} brightness Global brightness. - * @param {number} contrast Global contrast. - * @param {number} hue Global hue. - * @param {number} saturation Global saturation. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {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 + * @return {ol.render.webgl.polygonreplay.shader.Default.Locations} Locations. */ -/*ol.render.webgl.PolygonReplay.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent) { - var gl = context.getGL(); - - // bind the vertices buffer - goog.asserts.assert(!goog.isNull(this.verticesBuffer_), - 'verticesBuffer must not be null'); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); - - // bind the indices buffer - goog.asserts.assert(!goog.isNull(this.indicesBuffer_), - 'indicesBuffer must not be null'); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - +ol.render.webgl.PolygonReplay.prototype.setUpProgram_ = function(gl, context, size, pixelRatio) { // get the program var fragmentShader, vertexShader; fragmentShader = @@ -1893,7 +1872,7 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(co // get the locations var locations; - if (goog.isNull(this.defaultLocations_)) { + if (!this.defaultLocations_) { locations = new ol.render.webgl.polygonreplay.shader.Default .Locations(gl, program); this.defaultLocations_ = locations; @@ -1906,43 +1885,10 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(co // enable the vertex attrib arrays gl.enableVertexAttribArray(locations.a_position); gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 24, 0); + false, 8, 0); - gl.enableVertexAttribArray(locations.a_color); - gl.vertexAttribPointer(locations.a_color, 4, ol.webgl.FLOAT, - false, 24, 8); - - // set the "uniform" values - // TODO: use RTE to avoid jitter - var projectionMatrix = this.projectionMatrix_; - ol.vec.Mat4.makeTransform2D(projectionMatrix, - 0.0, 0.0, - pixelRatio * 2 / (resolution * size[0]), - pixelRatio * 2 / (resolution * size[1]), - -rotation, - -center[0], -center[1]); - - gl.uniformMatrix4fv(locations.u_projectionMatrix, false, projectionMatrix); - - // draw! - var result; - if (!goog.isDef(featureCallback)) { - this.drawReplay_(gl, context, skippedFeaturesHash); - } else { - // TODO: draw feature by feature for the hit-detection - } - - // disable the vertex attrib arrays - gl.disableVertexAttribArray(locations.a_position); - gl.disableVertexAttribArray(locations.a_color); - - this.lineStringReplay_.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent); - // FIXME get result - return result; -};*/ + return locations; +}; /** @@ -1952,44 +1898,58 @@ ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(co * @param {Object} skippedFeaturesHash Ids of features to skip. */ ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { - var elementType = context.hasOESElementIndexUint ? - ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; - // var elementSize = context.hasOESElementIndexUint ? 4 : 2; if (!ol.object.isEmpty(skippedFeaturesHash)) { // TODO: draw by blocks to skip features } else { - var numItems = this.indices_.length; - gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, 0); + goog.asserts.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices_[this.startIndices_.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + this.drawElements_(gl, context, start, end); + end = start; + } } }; +/** + * @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) { - // TODO implement - if (fillStyle) { - var fillStyleColor = fillStyle.getColor(); - this.fillColor_ = !goog.isNull(fillStyleColor) && Array.isArray(fillStyleColor) ? - ol.color.asArray(fillStyleColor).map(function(c, i) { - return i != 3 ? c / 255.0 : c; - }) : ol.render.webgl.defaultFillStyle; + goog.asserts.assert(this.state_, 'this.state_ should not be null'); + goog.asserts.assert(fillStyle, 'fillStyle should not be null'); + var fillStyleColor = ol.color.asArray(fillStyle.getColor()); + if (Array.isArray(fillStyleColor)) { + fillStyleColor = fillStyleColor.map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultFillStyle; } else { - this.fillColor_ = undefined; + fillStyleColor = ol.render.webgl.defaultFillStyle; } - if (strokeStyle) { - var strokeStyleColor = strokeStyle.getColor(); - this.strokeColor_ = !goog.isNull(strokeStyleColor) ? - ol.color.asArray(strokeStyleColor).map(function(c, i) { - return i != 3 ? c / 255 : c; - }) : ol.render.webgl.defaultStrokeStyle; - } else { - this.strokeColor_ = undefined; + if (!this.state_.fillColor || !ol.array.equals(fillStyleColor, this.state_.fillColor)) { + this.state_.fillColor = fillStyleColor; + this.state_.changed = true; + this.styles_.push(fillStyleColor); } - this.lineStringReplay_.setFillStrokeStyle(fillStyle, strokeStyle); + this.lineStringReplay_.setFillStrokeStyle(null, strokeStyle); }; @@ -2255,8 +2215,8 @@ ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( */ ol.render.webgl.BATCH_CONSTRUCTORS_ = { 'Image': ol.render.webgl.ImageReplay, - 'LineString': ol.render.webgl.LineStringReplay//, - //'Polygon': ol.render.webgl.PolygonReplay + 'LineString': ol.render.webgl.LineStringReplay, + 'Polygon': ol.render.webgl.PolygonReplay }; diff --git a/src/ol/render/webgl/webglpolygondefault.glsl b/src/ol/render/webgl/webglpolygondefault.glsl index 4d81a78a5c..fe31b75ad4 100644 --- a/src/ol/render/webgl/webglpolygondefault.glsl +++ b/src/ol/render/webgl/webglpolygondefault.glsl @@ -3,32 +3,29 @@ //! COMMON -varying vec4 v_color; //! VERTEX attribute vec2 a_position; -attribute vec4 a_color; uniform mat4 u_projectionMatrix; uniform mat4 u_offsetScaleMatrix; uniform mat4 u_offsetRotateMatrix; void main(void) { - v_color = a_color; - mat4 offsetMatrix = u_offsetScaleMatrix; - vec4 offsets = offsetMatrix * vec4(0., 0., 0., 0.); + vec4 offsets = u_offsetScaleMatrix * vec4(0., 0., 0., 0.); gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets; } //! FRAGMENT +uniform vec4 u_color; uniform float u_opacity; void main(void) { - gl_FragColor = v_color; - float alpha = v_color.a * u_opacity; + gl_FragColor = u_color; + float alpha = u_color.a * u_opacity; if (alpha == 0.0) { discard; } diff --git a/src/ol/render/webgl/webglpolygondefaultshader.js b/src/ol/render/webgl/webglpolygondefaultshader.js index 0b40c76874..1675d265a1 100644 --- a/src/ol/render/webgl/webglpolygondefaultshader.js +++ b/src/ol/render/webgl/webglpolygondefaultshader.js @@ -23,14 +23,14 @@ goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultFragment); * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec4 v_color;\n\n\n\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = v_color;\n float alpha = v_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; +ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; /** * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec4 a;uniform float g;void main(void){gl_FragColor=a;float alpha=a.a*g;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; +ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; /** @@ -58,14 +58,14 @@ goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultVertex); * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec4 v_color;\n\n\nattribute vec2 a_position;\nattribute vec4 a_color;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n v_color = a_color;\n mat4 offsetMatrix = u_offsetScaleMatrix;\n vec4 offsets = offsetMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; +ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n vec4 offsets = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec4 a;attribute vec2 b;attribute vec4 c;uniform mat4 d;uniform mat4 e;uniform mat4 f;void main(void){a=c;mat4 offsetMatrix=e;vec4 offsets=offsetMatrix*vec4(0.,0.,0.,0.);gl_Position=d*vec4(b,0.,1.)+offsets;}'; +ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){vec4 offsets=c*vec4(0.,0.,0.,0.);gl_Position=b*vec4(a,0.,1.)+offsets;}'; /** @@ -85,39 +85,39 @@ ol.render.webgl.polygonreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? */ ol.render.webgl.polygonreplay.shader.Default.Locations = function(gl, program) { + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, goog.DEBUG ? 'u_color' : 'e'); + /** * @type {WebGLUniformLocation} */ this.u_offsetRotateMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'f'); + program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'd'); /** * @type {WebGLUniformLocation} */ this.u_offsetScaleMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'e'); + program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'c'); /** * @type {WebGLUniformLocation} */ this.u_opacity = gl.getUniformLocation( - program, goog.DEBUG ? 'u_opacity' : 'g'); + program, goog.DEBUG ? 'u_opacity' : 'f'); /** * @type {WebGLUniformLocation} */ this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'd'); - - /** - * @type {number} - */ - this.a_color = gl.getAttribLocation( - program, goog.DEBUG ? 'a_color' : 'c'); + program, goog.DEBUG ? 'u_projectionMatrix' : 'b'); /** * @type {number} */ this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'b'); + program, goog.DEBUG ? 'a_position' : 'a'); }; From 112fae228a70d991338e66b6241f0317295f0e86 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Thu, 7 Jul 2016 18:13:26 +0200 Subject: [PATCH 43/88] Sanity check fill colors. --- src/ol/render/webgl/imagereplay/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 249d7e4408..71dcbe8d43 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1936,9 +1936,10 @@ ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) { ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { goog.asserts.assert(this.state_, 'this.state_ should not be null'); goog.asserts.assert(fillStyle, 'fillStyle should not be null'); - var fillStyleColor = ol.color.asArray(fillStyle.getColor()); - if (Array.isArray(fillStyleColor)) { - fillStyleColor = fillStyleColor.map(function(c, i) { + var fillStyleColor = fillStyle.getColor(); + 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 { From ce3b286cd37b615d3d5c66615eda4fac7c51a726 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Thu, 7 Jul 2016 19:48:01 +0200 Subject: [PATCH 44/88] RTE correct only once. --- src/ol/render/webgl/imagereplay/index.js | 32 ++++++++++++++---------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 71dcbe8d43..012baf54a0 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -11,6 +11,7 @@ goog.require('ol.extent'); goog.require('ol.obj'); goog.require('ol.render.ReplayGroup'); goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.transform'); goog.require('ol.geom.flat.topology'); goog.require('ol.render.VectorContext'); goog.require('ol.render.replay'); @@ -1122,17 +1123,17 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord n = numVertices / 7; p0 = p1; - p1 = p2 || [flatCoordinates[i] - this.origin_[0], flatCoordinates[i + 1] - this.origin_[1]]; + p1 = p2 || [flatCoordinates[i], flatCoordinates[i + 1]]; //First vertex. if (i === offset) { - p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; + 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] - this.origin_[0], - flatCoordinates[end - stride * 2 + 1] - this.origin_[1]]; + p0 = [flatCoordinates[end - stride * 2], + flatCoordinates[end - stride * 2 + 1]]; startCoords = p2; } else { @@ -1209,7 +1210,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord break; } } else { - p2 = [flatCoordinates[i + stride] - this.origin_[0], flatCoordinates[i + stride + 1] - this.origin_[1]]; + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; } sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) @@ -1326,6 +1327,8 @@ ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringG 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; @@ -1349,6 +1352,8 @@ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiL var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); var stride = lineStringGeometries[i].getStride(); if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin_[0], -this.origin_[1]); this.drawCoordinates_( flatCoordinates, 0, flatCoordinates.length, stride); } @@ -1707,14 +1712,7 @@ ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); */ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(flatCoordinates, holeIndices, stride) { // Triangulate the polygon - var nextIndex = this.vertices_.length / 2 + 1; - flatCoordinates = flatCoordinates.map(function(curr, index) { - if (index % 2 === 0) { - return curr - this.origin_[0]; - } else { - return curr - this.origin_[1]; - } - }, this); + var nextIndex = this.vertices_.length / 2; var triangulation = ol.ext.earcut(flatCoordinates, holeIndices, stride).map(function(curr) { return curr + nextIndex; }); @@ -1739,12 +1737,16 @@ ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygon var linearRings = polygons[i].getLinearRings(); if (linearRings.length > 0) { var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin_[0], -this.origin_[1]); this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); var holeIndices = []; var holeFlatCoords; for (j = 1, jj = linearRings.length; j < jj; ++j) { holeIndices.push(flatCoordinates.length / 2); holeFlatCoords = linearRings[i].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin_[0], -this.origin_[1]); ol.array.extend(flatCoordinates, holeFlatCoords); this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); } @@ -1793,12 +1795,16 @@ ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, } var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin_[0], -this.origin_[1]); this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); var holeIndices = []; var i, ii, holeFlatCoords; for (i = 1, ii = linearRings.length; i < ii; ++i) { holeIndices.push(flatCoordinates.length / 2); holeFlatCoords = linearRings[i].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin_[0], -this.origin_[1]); ol.array.extend(flatCoordinates, holeFlatCoords); this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); } From 16a50ee0c7c4ac626f2d5603bbbf8943692f3f6f Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 8 Jul 2016 17:03:01 +0200 Subject: [PATCH 45/88] Extend PolygonReplay with hit detection and skipping. --- src/ol/render/webgl/imagereplay/index.js | 131 +++++++++++++++++++++-- 1 file changed, 121 insertions(+), 10 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 012baf54a0..73be1175a7 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -26,6 +26,7 @@ goog.require('ol.render.webgl.polygonreplay.shader.Default'); goog.require('ol.render.webgl.polygonreplay.shader.Default.Locations'); goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); goog.require('ol.render.webgl.polygonreplay.shader.DefaultVertex'); +goog.require('ol.style.Stroke'); goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); @@ -1385,7 +1386,7 @@ ol.render.webgl.LineStringReplay.prototype.finish = function(context) { //Clean up, if there is nothing to draw if (this.styleIndices_.length === 0 && this.styles_.length > 0) { - this.styles_.pop(); + this.styles_ = []; } this.vertices_ = null; @@ -1822,16 +1823,21 @@ ol.render.webgl.PolygonReplay.prototype.finish = function(context) { this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); - var indices = this.indices_; - var bits = context.hasOESElementIndexUint ? 32 : 16; - goog.asserts.assert(indices[indices.length - 1] < Math.pow(2, bits), - 'Too large element index detected [%s] (OES_element_index_uint "%s")', - indices[indices.length - 1], context.hasOESElementIndexUint); - // create, bind, and populate the indices buffer - this.indicesBuffer_ = new ol.webgl.Buffer(indices); + this.indicesBuffer_ = new ol.webgl.Buffer(this.indices_); context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + + this.startIndices_.push(this.indices_.length); + this.lineStringReplay_.finish(context); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices_ = null; + this.indices_ = null; }; @@ -1906,7 +1912,7 @@ ol.render.webgl.PolygonReplay.prototype.setUpProgram_ = function(gl, context, si ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { if (!ol.object.isEmpty(skippedFeaturesHash)) { - // TODO: draw by blocks to skip features + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); } else { goog.asserts.assert(this.styles_.length === this.styleIndices_.length, 'number of styles and styleIndices match'); @@ -1925,6 +1931,102 @@ ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skip }; +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + goog.asserts.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices_.length - 2; + end = this.startIndices_[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices_[featureIndex] >= groupStart) { + start = this.startIndices_[featureIndex]; + feature = this.startIndicesFeature_[featureIndex]; + featureUid = goog.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements_(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices_.length - 2; + end = start = this.startIndices_[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices_[featureIndex] >= groupStart) { + featureStart = this.startIndices_[featureIndex]; + feature = this.startIndicesFeature_[featureIndex]; + featureUid = goog.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements_(gl, context, start, end); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements_(gl, context, start, end); + } + } +}; + + /** * @private * @param {WebGLRenderingContext} gl gl. @@ -1956,7 +2058,16 @@ ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, this.state_.changed = true; this.styles_.push(fillStyleColor); } - this.lineStringReplay_.setFillStrokeStyle(null, strokeStyle); + //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); + } }; From 226bf5a9eaf280d4366977be63b2f665dc1fac73 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 8 Jul 2016 17:19:55 +0200 Subject: [PATCH 46/88] Allow empty fillStyle for polygons. --- src/ol/render/webgl/imagereplay/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 73be1175a7..d0c99562c6 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -2043,8 +2043,8 @@ ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) { ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { goog.asserts.assert(this.state_, 'this.state_ should not be null'); - goog.asserts.assert(fillStyle, 'fillStyle should not be null'); - var fillStyleColor = fillStyle.getColor(); + goog.asserts.assert(fillStyle || strokeStyle, 'one of the styles should not be null'); + var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0]; if (!(fillStyleColor instanceof CanvasGradient) && !(fillStyleColor instanceof CanvasPattern)) { fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) { From 4b2fb389548b6abd5da137122111daa2d70c5082 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 8 Jul 2016 20:45:42 +0200 Subject: [PATCH 47/88] Fix preparation of multipolygons with holes. --- src/ol/render/webgl/imagereplay/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index d0c99562c6..497d5d7fb2 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1745,7 +1745,7 @@ ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygon var holeFlatCoords; for (j = 1, jj = linearRings.length; j < jj; ++j) { holeIndices.push(flatCoordinates.length / 2); - holeFlatCoords = linearRings[i].getFlatCoordinates(); + holeFlatCoords = linearRings[j].getFlatCoordinates(); holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, stride, -this.origin_[0], -this.origin_[1]); ol.array.extend(flatCoordinates, holeFlatCoords); From 18bc820f8bebbcb5fdb590eecc370a2d58768c53 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 13 Jul 2016 18:43:35 +0200 Subject: [PATCH 48/88] Prevent overdraw in PolygonReplay. --- src/ol/render/webgl/imagereplay/index.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 497d5d7fb2..5956e6deff 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1908,8 +1908,18 @@ ol.render.webgl.PolygonReplay.prototype.setUpProgram_ = function(gl, context, si * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {boolean} hitDetection Hit detection mode. */ -ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash) { +ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } if (!ol.object.isEmpty(skippedFeaturesHash)) { this.drawReplaySkipping_(gl, context, skippedFeaturesHash); @@ -1928,6 +1938,13 @@ ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skip end = start; } } + if (!hitDetection) { + gl.clear(gl.DEPTH_BUFFER_BIT); + gl.disable(gl.DEPTH_TEST); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } }; From 610084d456fbfb868752712bbd459cc90060e524 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 7 Sep 2016 11:16:59 +0200 Subject: [PATCH 49/88] Linked list structure for polygon renderer --- src/ol/structs/linkedlist.js | 247 +++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 src/ol/structs/linkedlist.js diff --git a/src/ol/structs/linkedlist.js b/src/ol/structs/linkedlist.js new file mode 100644 index 0000000000..7b51fde9fe --- /dev/null +++ b/src/ol/structs/linkedlist.js @@ -0,0 +1,247 @@ +goog.provide('ol.structs.LinkedList'); + +/** + * Creates an empty linked list structure. + * + * @constructor + * @param {boolean=} opt_circular The last item is connected to the first one, + * and the first item to the last one. Default is true. + */ +ol.structs.LinkedList = function(opt_circular) { + + /** + * @private + * @type {ol.LinkedListItem|undefined} + */ + this.first_ = undefined; + + /** + * @private + * @type {ol.LinkedListItem|undefined} + */ + this.last_ = undefined; + + /** + * @private + * @type {ol.LinkedListItem|undefined} + */ + this.head_ = undefined; + + /** + * @private + * @type {boolean} + */ + this.circular_ = opt_circular === undefined ? true : opt_circular; + + /** + * @private + * @type {number} + */ + this.length_ = 0; +}; + +/** + * Inserts an item into the linked list right after the current one. + * + * @param {?} data Item data. + */ +ol.structs.LinkedList.prototype.insertItem = function(data) { + + /** @type {ol.LinkedListItem} */ + var item = { + prev: undefined, + next: undefined, + data: data + }; + + var head = this.head_; + + //Initialize the list. + if (!head) { + this.first_ = item; + this.last_ = item; + if (this.circular_) { + item.next = item; + item.prev = item; + } + } else { + //Link the new item to the adjacent ones. + var next = head.next; + item.prev = head; + item.next = next; + head.next = item; + if (next) { + next.prev = item; + } + + if (head === this.last_) { + this.last_ = item; + } + } + this.head_ = item; + this.length_++; +}; + +/** + * Removes the current item from the list. Sets the cursor to the next item, + * if possible. + */ +ol.structs.LinkedList.prototype.removeItem = function() { + var head = this.head_; + if (head) { + var next = head.next; + var prev = head.prev; + if (next) { + next.prev = prev; + } + if (prev) { + prev.next = next; + } + this.head_ = next || prev; + + if (this.first_ === this.last_) { + this.head_ = undefined; + this.first_ = undefined; + this.last_ = undefined; + } else if (this.first_ === head) { + this.first_ = this.head_; + } else if (this.last_ === head) { + this.last_ = prev ? this.head_.prev : this.head_; + } + this.length_--; + } +}; + +/** + * Sets the cursor to the first item, and returns the associated data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.firstItem = function() { + this.head_ = this.first_; + if (this.head_) { + return this.head_.data; + } + return undefined; +}; + +/** +* Sets the cursor to the last item, and returns the associated data. +* +* @return {?} Item data. +*/ +ol.structs.LinkedList.prototype.lastItem = function() { + this.head_ = this.last_; + if (this.head_) { + return this.head_.data; + } + return undefined; +}; + +/** + * Sets the cursor to the next item, and returns the associated data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.nextItem = function() { + if (this.head_ && this.head_.next) { + this.head_ = this.head_.next; + return this.head_.data; + } + return undefined; +}; + +/** + * Returns the next item's data without moving the cursor. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.getNextItem = function() { + if (this.head_ && this.head_.next) { + return this.head_.next.data; + } + return undefined; +}; + +/** + * Sets the cursor to the previous item, and returns the associated data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.prevItem = function() { + if (this.head_ && this.head_.prev) { + this.head_ = this.head_.prev; + return this.head_.data; + } + return undefined; +}; + +/** + * Returns the previous item's data without moving the cursor. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.getPrevItem = function() { + if (this.head_ && this.head_.prev) { + return this.head_.prev.data; + } + return undefined; +}; + +/** + * Returns the current item's data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.getCurrItem = function() { + if (this.head_) { + return this.head_.data; + } + return undefined; +}; + +/** + * Sets the first item of the list. This only works for circular lists, and sets + * the last item accordingly. + */ +ol.structs.LinkedList.prototype.setFirstItem = function() { + if (this.circular_ && this.head_) { + this.first_ = this.head_; + this.last_ = this.head_.prev; + } +}; + +/** + * Concatenates two lists. + * @param {ol.structs.LinkedList} list List to merge into the current list. + */ +ol.structs.LinkedList.prototype.concat = function(list) { + if (list.head_) { + if (this.head_) { + var end = this.head_.next; + this.head_.next = list.first_; + list.first_.prev = this.head_; + end.prev = list.last_; + list.last_.next = end; + this.length_ += list.length_; + } else { + this.head_ = list.head_; + this.first_ = list.first_; + this.last_ = list.last_; + this.length_ = list.length_; + } + list.head_ = undefined; + list.first_ = undefined; + list.last_ = undefined; + list.length_ = 0; + } +}; + +/** + * Returns the current length of the list. + * + * @return {number} Length. + */ +ol.structs.LinkedList.prototype.getLength = function() { + return this.length_; +}; From 88f7e7a38d7b7fa0736b7fcab0e0271182d63144 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Thu, 8 Sep 2016 10:04:36 +0200 Subject: [PATCH 50/88] Utilities for polygon renderer --- src/ol/render/webgl/webgl.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/ol/render/webgl/webgl.js b/src/ol/render/webgl/webgl.js index 498ff2d72f..dbaafc7e77 100644 --- a/src/ol/render/webgl/webgl.js +++ b/src/ol/render/webgl/webgl.js @@ -59,3 +59,24 @@ ol.render.webgl.lineStringInstruction = { MITER_BOTTOM: 19, MITER_TOP: 23 }; + +/** + * Calcualtes 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 === 0 ? undefined : area > 0; +}; + +/** + * @const + * @type {number} + */ +ol.render.webgl.EPSILON = Number.EPSILON || 2.220446049250313e-16; From 5d65028d269c33276bcef90a7a87ad43f95894f8 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 12 Sep 2016 16:21:50 +0200 Subject: [PATCH 51/88] Initial ear clipping algorithm --- src/ol/render/webgl/imagereplay/index.js | 550 ++++++++++++++++++++++- src/ol/render/webgl/webgl.js | 3 +- src/ol/typedefs.js | 17 + 3 files changed, 547 insertions(+), 23 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 5956e6deff..3388c11d02 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -6,7 +6,6 @@ goog.provide('ol.render.webgl.ReplayGroup'); goog.require('ol'); goog.require('ol.color'); -goog.require('ol.ext.earcut'); goog.require('ol.extent'); goog.require('ol.obj'); goog.require('ol.render.ReplayGroup'); @@ -27,6 +26,8 @@ goog.require('ol.render.webgl.polygonreplay.shader.Default.Locations'); goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); goog.require('ol.render.webgl.polygonreplay.shader.DefaultVertex'); goog.require('ol.style.Stroke'); +goog.require('ol.structs.LinkedList'); +goog.require('ol.structs.RBush'); goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); @@ -1214,8 +1215,8 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; } - sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) - ? 1 : -1; + sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) + ? -1 : 1; numVertices = this.addVertices_(p0, p1, p2, sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); @@ -1700,6 +1701,11 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { changed: false }; + /** + * @private + */ + this.rtree_ = new ol.structs.RBush(); + }; ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); @@ -1707,21 +1713,525 @@ ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); /** * Draw one polygon. * @param {Array.} flatCoordinates Flat coordinates. - * @param {Array.|null} holeIndices Hole indices. + * @param {Array.>} holeFlatCoordinates Hole flat coordinates. * @param {number} stride Stride. * @private */ -ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(flatCoordinates, holeIndices, stride) { +ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( + flatCoordinates, holeFlatCoordinates, stride) { // Triangulate the polygon - var nextIndex = this.vertices_.length / 2; - var triangulation = ol.ext.earcut(flatCoordinates, holeIndices, stride).map(function(curr) { - return curr + nextIndex; - }); + var outerRing = new ol.structs.LinkedList(); + // Initialize the outer ring + var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, true); - if (triangulation.length > 2) { - ol.array.extend(this.vertices_, flatCoordinates); - ol.array.extend(this.indices_, triangulation); + // Eliminate holes, if there are any + if (holeFlatCoordinates.length) { + var i, ii; + var holeLists = []; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + var holeList = { + list: new ol.structs.LinkedList(), + maxX: undefined + }; + holeLists.push(holeList); + holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i], + stride, holeList.list, false); + } + holeLists.sort(function(a, b) { + return b.maxX - a.maxX; + }); + for (i = 0; i < holeLists.length; ++i) { + this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX); + } } + this.classifyPoints_(outerRing, false); + this.triangulate_(outerRing); + + // We clear the R-Tree here, because hit detection does not call finish() + this.rtree_.clear(); +}; + + +/** + * 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 {boolean} clockwise Coordinate order should be clockwise. + * @return {number} Maximum X value. + */ +ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( + flatCoordinates, stride, list, clockwise) { + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, + 0, flatCoordinates.length, stride); + var i, ii, maxX; + var n = this.vertices_.length / 2; + /** @type {ol.WebglPolygonVertex} */ + var start; + /** @type {ol.WebglPolygonVertex} */ + var p0; + /** @type {ol.WebglPolygonVertex} */ + var p1; + if (clockwise === isClockwise) { + start = this.createPoint_(flatCoordinates[0], flatCoordinates[1], n++); + p0 = start; + maxX = flatCoordinates[0]; + for (i = stride, ii = flatCoordinates.length; i < ii; i += stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + this.insertItem_(p0, p1, list); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + this.insertItem_(p1, start, list); + } else { + var end = flatCoordinates.length - stride; + start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++); + p0 = start; + maxX = flatCoordinates[end]; + for (i = end - stride, ii = 0; i >= ii; i -= stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + this.insertItem_(p0, p1, list); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + this.insertItem_(p1, start, list); + } + + return maxX; +}; + + +/** + * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices. + * @private + * @param {ol.structs.LinkedList} list Polygon ring. + * @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, 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); + 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. + */ +ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, + list, listMaxX) { + 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}, true); + for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { + var currSeg = intersectingSegments[i]; + if (currSeg.p0 !== p1 && currSeg.p1 !== p1) { + var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, + currSeg.p1, true); + var dist = Math.abs(p1.x - intersection[0]); + if (dist < minDist) { + minDist = dist; + p5 = {x: intersection[0], y: intersection[1], i: -1}; + seg = currSeg; + } + } + } + bestPoint = seg.p1; + + var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1); + if (pointsInTriangle.length) { + var theta = Infinity; + for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) { + var currPoint = pointsInTriangle[i]; + var currTheta = Math.atan2(p1.y - currPoint.y, p2.x - currPoint.x); + if (currTheta < theta || (currTheta === theta && currPoint.x < bestPoint.x)) { + theta = currTheta; + bestPoint = currPoint; + } + } + } + + seg = list.firstItem(); + while (seg.p1 !== bestPoint) { + seg = list.nextItem(); + } + + //We clone the bridge points as they can have different convexity. + var p0Bridge = {x: p1.x, y: p1.y, i: p1.i, reflex: undefined}; + var p1Bridge = {x: seg.p1.x, y: seg.p1.y, i: seg.p1.i, reflex: undefined}; + + hole.getNextItem().p0 = p0Bridge; + this.insertItem_(p1, seg.p1, hole); + this.insertItem_(p1Bridge, p0Bridge, hole); + seg.p1 = p1Bridge; + hole.setFirstItem(); + list.concat(hole); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list) { + var ccw = false; + var simple = this.isSimple_(list); + var pass = 0; + + // Start clipping ears + while (list.getLength() > 3) { + if (simple) { + if (!this.clipEars_(list, simple, ccw)) { + if (!this.classifyPoints_(list, ccw)) { + // We have the wrongly oriented remains of a self-intersecting polygon. + pass++; + if (pass > 1) { + // Something went wrong. + break; + } + ccw = !ccw; + this.classifyPoints_(list, ccw); + } + } + } else { + if (!this.clipEars_(list, simple, ccw)) { + // We ran out of ears, try to reclassify. + if (!this.classifyPoints_(list, ccw)) { + // We have a bad polygon, try to resolve local self-intersections. + if (!this.resolveLocalSelfIntersections_(list)) { + simple = this.isSimple_(list); + if (!simple) { + // We have a really bad polygon, try more time consuming methods. + break; + } + } + } + } + } + } + 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 {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, simple, ccw) { + var numIndices = this.indices_.length; + var start = list.firstItem(); + var s0 = list.getPrevItem(); + var s1 = start; + var s2 = list.nextItem(); + var s3 = list.getNextItem(); + var p0, p1, p2; + var processedEars = false; + do { + p0 = s1.p0; + p1 = s1.p1; + p2 = s2.p1; + if (p1.reflex === false) { + // We might have a valid ear + var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0, + s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1); + if ((simple || this.getIntersections_({p0: p0, p1: p2}).length === 0) && + diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, true).length === 0) { + //The diagonal is completely inside the polygon + if (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); + 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. + * @return {boolean} There were resolved intersections. +*/ +ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function( + list) { + 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); + if (intersection) { + var numVertices = this.vertices_.length; + var numIndices = this.indices_.length; + var n = numVertices / 2; + var p = this.createPoint_(intersection[0], intersection[1], n); + + var seg = list.prevItem(); + list.removeItem(); + this.rtree_.remove(seg); + s0.p1 = p; + s1.p0 = p; + this.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); + this.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 (start === seg) { + break; + } + } + + s0 = list.getPrevItem(); + s1 = list.nextItem(); + } while (s0 !== start); + return resolvedIntersections; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @return {boolean} The polygon is simple. + */ +ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list) { + var start = list.firstItem(); + var seg = start; + do { + if (this.getIntersections_(seg).length) { + return false; + } + seg = list.nextItem(); + } while (seg !== start); + return true; +}; + + +/** + * @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. + */ +ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list) { + var seg = { + p0: p0, + p1: p1 + }; + list.insertItem(seg); + this.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); +}; + + + /** + * @private + * @param {Object.} s0 Segment before the remove candidate. + * @param {Object.} s1 Remove candidate segment. + * @param {ol.structs.LinkedList} list Polygon ring. + */ +ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list) { + list.removeItem(); + s0.p1 = s1.p1; + this.rtree_.remove(s1); + this.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 {boolean=} opt_reflex Only include reflex points. + * @return {Array.>} Points in the triangle. + */ +ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, + p2, opt_reflex) { + var i, ii, j, p; + var result = []; + var segmentsInExtent = this.rtree_.getInExtent([Math.min(p0.x, p1.x, p2.x), + Math.min(p0.y, p1.y, p2.y), Math.max(p0.x, p1.x, p2.x), Math.max(p0.y, + p1.y, p2.y)]); + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + for (j in segmentsInExtent[i]) { + p = segmentsInExtent[i][j]; + if (p.x && p.y && (!opt_reflex || p.reflex)) { + if ((p.x !== p0.x || p.y !== p0.y) && (p.x !== p1.x || p.y !== p1.y) && + (p.x !== p2.x || p.y !== p2.y) && result.indexOf(p) === -1 && + ol.geom.flat.contains.linearRingContainsXY([p0.x, p0.y, p1.x, p1.y, + p2.x, p2.y], 0, 6, 2, p.x, p.y)) { + result.push(p); + } + } + } + } + return result; +}; + + +/** + * @private + * @param {Object.} segment Segment. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.>} Intersecting segments. + */ +ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, opt_touch) { + var p0 = segment.p0; + var p1 = segment.p1; + var segmentsInExtent = this.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; }; @@ -1741,18 +2251,16 @@ ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygon flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, stride, -this.origin_[0], -this.origin_[1]); this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); - var holeIndices = []; + var holes = []; var holeFlatCoords; for (j = 1, jj = linearRings.length; j < jj; ++j) { - holeIndices.push(flatCoordinates.length / 2); holeFlatCoords = linearRings[j].getFlatCoordinates(); holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, stride, -this.origin_[0], -this.origin_[1]); - ol.array.extend(flatCoordinates, holeFlatCoords); + holes.push(holeFlatCoords); this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); } - holeIndices = holeIndices.length === 0 ? null : holeIndices; - this.drawCoordinates_(flatCoordinates, holeIndices, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); } } if (this.indices_.length > currIndex) { @@ -1799,18 +2307,16 @@ ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, stride, -this.origin_[0], -this.origin_[1]); this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); - var holeIndices = []; + var holes = []; var i, ii, holeFlatCoords; for (i = 1, ii = linearRings.length; i < ii; ++i) { - holeIndices.push(flatCoordinates.length / 2); holeFlatCoords = linearRings[i].getFlatCoordinates(); holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, stride, -this.origin_[0], -this.origin_[1]); - ol.array.extend(flatCoordinates, holeFlatCoords); + holes.push(holeFlatCoords); this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); } - holeIndices = holeIndices.length === 0 ? null : holeIndices; - this.drawCoordinates_(flatCoordinates, holeIndices, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); } }; diff --git a/src/ol/render/webgl/webgl.js b/src/ol/render/webgl/webgl.js index dbaafc7e77..564bdad94a 100644 --- a/src/ol/render/webgl/webgl.js +++ b/src/ol/render/webgl/webgl.js @@ -72,7 +72,8 @@ ol.render.webgl.lineStringInstruction = { */ ol.render.webgl.triangleIsCounterClockwise = function(x1, y1, x2, y2, x3, y3) { var area = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1); - return area === 0 ? undefined : area > 0; + return (area <= ol.render.webgl.EPSILON && area >= -ol.render.webgl.EPSILON) ? + undefined : area > 0; }; /** diff --git a/src/ol/typedefs.js b/src/ol/typedefs.js index 90a9684a66..5016d782b0 100644 --- a/src/ol/typedefs.js +++ b/src/ol/typedefs.js @@ -317,6 +317,14 @@ ol.LayerState; ol.LayoutOptions; +/** + * @typedef {{prev: (ol.LinkedListItem|undefined), + * next: (ol.LinkedListItem|undefined), + * data: ?}} + */ +ol.LinkedListItem; + + /** * A function that takes an {@link ol.Extent} and a resolution as arguments, and * returns an array of {@link ol.Extent} with the extents to load. Usually this @@ -686,6 +694,15 @@ ol.ViewAnimation; ol.WebglBufferCacheEntry; +/** + * @typedef {{x: number, + * y: number, + * i: number, + * reflex: (boolean|undefined)}} + */ +ol.WebglPolygonVertex; + + /** * @typedef {{magFilter: number, minFilter: number, texture: WebGLTexture}} */ From 657f2c7b6c0f300c5be084d7dce439e8c810a0de Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 13 Sep 2016 16:08:21 +0200 Subject: [PATCH 52/88] Optimize processing flat coordinates --- src/ol/render/webgl/imagereplay/index.js | 34 +++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 3388c11d02..153ea06517 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1773,17 +1773,23 @@ ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( var p0; /** @type {ol.WebglPolygonVertex} */ var p1; + var extents = []; + var segments = []; if (clockwise === isClockwise) { start = this.createPoint_(flatCoordinates[0], flatCoordinates[1], n++); p0 = start; maxX = flatCoordinates[0]; for (i = stride, ii = flatCoordinates.length; i < ii; i += stride) { p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); - this.insertItem_(p0, p1, list); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; p0 = p1; } - this.insertItem_(p1, start, list); + 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++); @@ -1791,12 +1797,17 @@ ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( maxX = flatCoordinates[end]; for (i = end - stride, ii = 0; i >= ii; i -= stride) { p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); - this.insertItem_(p0, p1, list); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; p0 = p1; } - this.insertItem_(p1, start, list); + 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)]); } + this.rtree_.load(extents, segments); return maxX; }; @@ -1899,8 +1910,8 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, 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); - this.insertItem_(p1Bridge, p0Bridge, hole); + this.insertItem_(p1, seg.p1, hole, true); + this.insertItem_(p1Bridge, p0Bridge, hole, true); seg.p1 = p1Bridge; hole.setFirstItem(); list.concat(hole); @@ -2101,15 +2112,20 @@ ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { * @param {ol.WebglPolygonVertex} p0 First point of segment. * @param {ol.WebglPolygonVertex} p1 Second point of segment. * @param {ol.structs.LinkedList} list Polygon ring. + * @param {boolean=} opt_rtree Insert the segment into the R-Tree. + * @return {Object.} segment. */ -ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list) { +ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) { var seg = { p0: p0, p1: p1 }; list.insertItem(seg); - this.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); + if (opt_rtree) { + this.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; }; From a0089b51266f6c4ffe46010ea53cd0463b6b3b0a Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Sun, 2 Oct 2016 12:25:02 +0200 Subject: [PATCH 53/88] Correctly triangulate bad polygons. --- src/ol/render/webgl/imagereplay/index.js | 160 +++++++++++++++-------- src/ol/typedefs.js | 6 + 2 files changed, 110 insertions(+), 56 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 153ea06517..754213f15e 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1701,11 +1701,6 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { changed: false }; - /** - * @private - */ - this.rtree_ = new ol.structs.RBush(); - }; ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); @@ -1721,8 +1716,9 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( flatCoordinates, holeFlatCoordinates, stride) { // Triangulate the polygon var outerRing = new ol.structs.LinkedList(); + var rtree = new ol.structs.RBush(); // Initialize the outer ring - var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, true); + var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true); // Eliminate holes, if there are any if (holeFlatCoordinates.length) { @@ -1735,20 +1731,17 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( }; holeLists.push(holeList); holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i], - stride, holeList.list, false); + stride, holeList.list, rtree, false); } holeLists.sort(function(a, b) { return b.maxX - a.maxX; }); for (i = 0; i < holeLists.length; ++i) { - this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX); + this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX, rtree); } } - this.classifyPoints_(outerRing, false); - this.triangulate_(outerRing); - - // We clear the R-Tree here, because hit detection does not call finish() - this.rtree_.clear(); + this.classifyPoints_(outerRing, rtree, false); + this.triangulate_(outerRing, rtree); }; @@ -1758,11 +1751,12 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( * @param {Array.} flatCoordinates Flat coordinates. * @param {number} stride Stride. * @param {ol.structs.LinkedList} list Linked list. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. * @param {boolean} clockwise Coordinate order should be clockwise. * @return {number} Maximum X value. */ ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( - flatCoordinates, stride, list, clockwise) { + flatCoordinates, stride, list, rtree, clockwise) { var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, 0, flatCoordinates.length, stride); var i, ii, maxX; @@ -1807,7 +1801,7 @@ ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( 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)]); } - this.rtree_.load(extents, segments); + rtree.load(extents, segments); return maxX; }; @@ -1817,10 +1811,11 @@ ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( * 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, ccw) { +ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) { var start = list.firstItem(); var s0 = start; var s1 = list.nextItem(); @@ -1831,7 +1826,7 @@ ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, ccw) { 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); + this.removeItem_(s0, s1, list, rtree); pointsReclassified = true; if (s1 === start) { start = list.getNextItem(); @@ -1855,9 +1850,10 @@ ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, ccw) { * @param {number} holeMaxX Maximum X value of the hole. * @param {ol.structs.LinkedList} list Linked list of the polygon. * @param {number} listMaxX Maximum X value of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. */ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, - list, listMaxX) { + list, listMaxX, rtree) { var seg = hole.firstItem(); while (seg.p1.x !== holeMaxX) { seg = hole.nextItem(); @@ -1871,7 +1867,7 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, /** @type {ol.WebglPolygonVertex} */ var p5; - var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, true); + var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { var currSeg = intersectingSegments[i]; if (currSeg.p0 !== p1 && currSeg.p1 !== p1) { @@ -1887,7 +1883,7 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, } bestPoint = seg.p1; - var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1); + var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree); if (pointsInTriangle.length) { var theta = Infinity; for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) { @@ -1910,8 +1906,8 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, 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, true); - this.insertItem_(p1Bridge, p0Bridge, hole, true); + this.insertItem_(p1, seg.p1, hole, rtree); + this.insertItem_(p1Bridge, p0Bridge, hole, rtree); seg.p1 = p1Bridge; hole.setFirstItem(); list.concat(hole); @@ -1921,17 +1917,18 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, /** * @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) { +ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { var ccw = false; - var simple = this.isSimple_(list); + var simple = this.isSimple_(list, rtree); var pass = 0; // Start clipping ears while (list.getLength() > 3) { if (simple) { - if (!this.clipEars_(list, simple, ccw)) { - if (!this.classifyPoints_(list, ccw)) { + if (!this.clipEars_(list, rtree, simple, ccw)) { + if (!this.classifyPoints_(list, rtree, ccw)) { // We have the wrongly oriented remains of a self-intersecting polygon. pass++; if (pass > 1) { @@ -1939,18 +1936,19 @@ ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list) { break; } ccw = !ccw; - this.classifyPoints_(list, ccw); + this.classifyPoints_(list, rtree, ccw); } } } else { - if (!this.clipEars_(list, simple, ccw)) { + if (!this.clipEars_(list, rtree, simple, ccw)) { // We ran out of ears, try to reclassify. - if (!this.classifyPoints_(list, ccw)) { + if (!this.classifyPoints_(list, rtree, ccw)) { // We have a bad polygon, try to resolve local self-intersections. - if (!this.resolveLocalSelfIntersections_(list)) { - simple = this.isSimple_(list); + if (!this.resolveLocalSelfIntersections_(list, rtree)) { + simple = this.isSimple_(list, rtree); if (!simple) { // We have a really bad polygon, try more time consuming methods. + this.splitPolygon_(list, rtree); break; } } @@ -1970,11 +1968,12 @@ ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list) { /** * @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, simple, ccw) { +ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple, ccw) { var numIndices = this.indices_.length; var start = list.firstItem(); var s0 = list.getPrevItem(); @@ -1991,8 +1990,8 @@ ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, simple, ccw) // We might have a valid ear var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0, s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1); - if ((simple || this.getIntersections_({p0: p0, p1: p2}).length === 0) && - diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, true).length === 0) { + if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) && + diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) { //The diagonal is completely inside the polygon if (p0.reflex === false || p2.reflex === false || ol.geom.flat.orient.linearRingIsClockwise([s0.p0.x, s0.p0.y, p0.x, @@ -2001,7 +2000,7 @@ ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, simple, ccw) this.indices_[numIndices++] = p0.i; this.indices_[numIndices++] = p1.i; this.indices_[numIndices++] = p2.i; - this.removeItem_(s1, s2, list); + this.removeItem_(s1, s2, list, rtree); if (s2 === start) { start = s3; } @@ -2023,10 +2022,11 @@ ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, simple, ccw) /** * @private * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. * @return {boolean} There were resolved intersections. */ ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function( - list) { + list, rtree) { var start = list.firstItem(); list.nextItem(); var s0 = start; @@ -2043,12 +2043,12 @@ ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = functio var seg = list.prevItem(); list.removeItem(); - this.rtree_.remove(seg); + rtree.remove(seg); s0.p1 = p; s1.p0 = p; - this.rtree_.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + 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); - this.rtree_.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y), + 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; @@ -2070,13 +2070,14 @@ ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = functio /** * @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) { +ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) { var start = list.firstItem(); var seg = start; do { - if (this.getIntersections_(seg).length) { + if (this.getIntersections_(seg, rtree).length) { return false; } seg = list.nextItem(); @@ -2085,6 +2086,50 @@ ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list) { }; +/** + * @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. @@ -2112,8 +2157,8 @@ ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { * @param {ol.WebglPolygonVertex} p0 First point of segment. * @param {ol.WebglPolygonVertex} p1 Second point of segment. * @param {ol.structs.LinkedList} list Polygon ring. - * @param {boolean=} opt_rtree Insert the segment into the R-Tree. - * @return {Object.} segment. + * @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 = { @@ -2122,7 +2167,7 @@ ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt }; list.insertItem(seg); if (opt_rtree) { - this.rtree_.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), + 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; @@ -2131,15 +2176,16 @@ ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt /** * @private - * @param {Object.} s0 Segment before the remove candidate. - * @param {Object.} s1 Remove candidate segment. + * @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) { +ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) { list.removeItem(); s0.p1 = s1.p1; - this.rtree_.remove(s1); - this.rtree_.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + 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); }; @@ -2149,14 +2195,15 @@ ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list) { * @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. + * @return {Array.} Points in the triangle. */ ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, - p2, opt_reflex) { + p2, rtree, opt_reflex) { var i, ii, j, p; var result = []; - var segmentsInExtent = this.rtree_.getInExtent([Math.min(p0.x, p1.x, p2.x), + 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) { @@ -2178,14 +2225,15 @@ ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, /** * @private - * @param {Object.} segment Segment. + * @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. + * @return {Array.} Intersecting segments. */ -ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, opt_touch) { +ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) { var p0 = segment.p0; var p1 = segment.p1; - var segmentsInExtent = this.rtree_.getInExtent([Math.min(p0.x, p1.x), + 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; diff --git a/src/ol/typedefs.js b/src/ol/typedefs.js index 5016d782b0..68782fbf3c 100644 --- a/src/ol/typedefs.js +++ b/src/ol/typedefs.js @@ -694,6 +694,12 @@ ol.ViewAnimation; ol.WebglBufferCacheEntry; +/** + * @typedef {{p0: ol.WebglPolygonVertex, + * p1: ol.WebglPolygonVertex}} + */ +ol.WebglPolygonSegment; + /** * @typedef {{x: number, * y: number, From dc27f768d06a3641fae57867326d5905fad685b4 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 3 Oct 2016 23:41:59 +0200 Subject: [PATCH 54/88] Correctly triangulate simple polygons. --- src/ol/render/webgl/imagereplay/index.js | 77 ++++++++++++++++++------ 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 754213f15e..f7b5ae6122 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1922,21 +1922,18 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { var ccw = false; var simple = this.isSimple_(list, rtree); - var pass = 0; // Start clipping ears while (list.getLength() > 3) { if (simple) { if (!this.clipEars_(list, rtree, simple, ccw)) { if (!this.classifyPoints_(list, rtree, ccw)) { - // We have the wrongly oriented remains of a self-intersecting polygon. - pass++; - if (pass > 1) { + // Due to the behavior of OL's PIP algorithm, the ear clipping cannot + // introduce touching segments. However, the original data may have some. + if (!this.resolveLocalSelfIntersections_(list, rtree, true)) { // Something went wrong. break; } - ccw = !ccw; - this.classifyPoints_(list, rtree, ccw); } } } else { @@ -1950,6 +1947,9 @@ ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { // 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); } } } @@ -1993,7 +1993,7 @@ ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) && diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) { //The diagonal is completely inside the polygon - if (p0.reflex === false || p2.reflex === false || + 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 @@ -2023,10 +2023,11 @@ ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple * @private * @param {ol.structs.LinkedList} list Linked list of the polygon. * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Resolve touching segments. * @return {boolean} There were resolved intersections. */ ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function( - list, rtree) { + list, rtree, opt_touch) { var start = list.firstItem(); list.nextItem(); var s0 = start; @@ -2034,28 +2035,48 @@ ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = functio var resolvedIntersections = false; do { - var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1); + 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 p = this.createPoint_(intersection[0], intersection[1], n); - var seg = list.prevItem(); list.removeItem(); rtree.remove(seg); - 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); + 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 (start === seg) { + if (breakCond) { break; } } @@ -2086,6 +2107,26 @@ ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) { }; +/** + * @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. From 360e77481ddee323bceb1fde2a93879341c256cd Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 5 Oct 2016 10:00:45 +0200 Subject: [PATCH 55/88] Restructure webgl replays --- package.json | 4 +- .../flat/{topologyflatgeom.js => topology.js} | 0 src/ol/render/webgl/imagereplay/index.js | 2481 +---------------- src/ol/render/webgl/{webgl.js => index.js} | 0 .../render/webgl/{webgl.jsdoc => index.jsdoc} | 0 .../defaultshader.glsl} | 4 +- .../defaultshader.js} | 79 +- src/ol/render/webgl/linestringreplay/index.js | 654 +++++ .../defaultshader.glsl} | 4 +- .../webgl/polygonreplay/defaultshader.js | 126 + src/ol/render/webgl/polygonreplay/index.js | 1018 +++++++ src/ol/render/webgl/replay.js | 321 +++ src/ol/render/webgl/replaygroup.js | 280 ++ .../render/webgl/webglpolygondefaultshader.js | 123 - .../ol/geom/flat/topologyflatgeom.test.js | 4 +- test/spec/ol/render/webgl/replay.test.js | 132 +- 16 files changed, 2597 insertions(+), 2633 deletions(-) rename src/ol/geom/flat/{topologyflatgeom.js => topology.js} (100%) rename src/ol/render/webgl/{webgl.js => index.js} (100%) rename src/ol/render/webgl/{webgl.jsdoc => index.jsdoc} (100%) rename src/ol/render/webgl/{webgllinestringdefault.glsl => linestringreplay/defaultshader.glsl} (97%) rename src/ol/render/webgl/{webgllinestringdefaultshader.js => linestringreplay/defaultshader.js} (80%) create mode 100644 src/ol/render/webgl/linestringreplay/index.js rename src/ol/render/webgl/{webglpolygondefault.glsl => polygonreplay/defaultshader.glsl} (82%) create mode 100644 src/ol/render/webgl/polygonreplay/defaultshader.js create mode 100644 src/ol/render/webgl/polygonreplay/index.js create mode 100644 src/ol/render/webgl/replay.js create mode 100644 src/ol/render/webgl/replaygroup.js delete mode 100644 src/ol/render/webgl/webglpolygondefaultshader.js diff --git a/package.json b/package.json index fd446ace18..0613b80f49 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "browserify": "13.1.1", "closure-util": "1.15.1", "derequire": "2.0.3", - "earcut": "2.1.1", "fs-extra": "1.0.0", "glob": "7.1.1", "handlebars": "4.0.6", @@ -124,7 +123,6 @@ "module": "vector-tile", "name": "vectortile", "browserify": true - }, - "earcut" + } ] } diff --git a/src/ol/geom/flat/topologyflatgeom.js b/src/ol/geom/flat/topology.js similarity index 100% rename from src/ol/geom/flat/topologyflatgeom.js rename to src/ol/geom/flat/topology.js diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index f7b5ae6122..402546cf16 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1,322 +1,21 @@ goog.provide('ol.render.webgl.ImageReplay'); -goog.provide('ol.render.webgl.LineStringReplay'); -goog.provide('ol.render.webgl.PolygonReplay'); -goog.provide('ol.render.webgl.Replay'); -goog.provide('ol.render.webgl.ReplayGroup'); goog.require('ol'); -goog.require('ol.color'); goog.require('ol.extent'); goog.require('ol.obj'); -goog.require('ol.render.ReplayGroup'); -goog.require('ol.geom.flat.orient'); -goog.require('ol.geom.flat.transform'); -goog.require('ol.geom.flat.topology'); -goog.require('ol.render.VectorContext'); -goog.require('ol.render.replay'); goog.require('ol.render.webgl.imagereplay.defaultshader'); -goog.require('ol.transform'); +goog.require('ol.render.webgl.Replay'); goog.require('ol.render.webgl'); -goog.require('ol.render.webgl.linestringreplay.shader.Default'); -goog.require('ol.render.webgl.linestringreplay.shader.Default.Locations'); -goog.require('ol.render.webgl.linestringreplay.shader.DefaultFragment'); -goog.require('ol.render.webgl.linestringreplay.shader.DefaultVertex'); -goog.require('ol.render.webgl.polygonreplay.shader.Default'); -goog.require('ol.render.webgl.polygonreplay.shader.Default.Locations'); -goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); -goog.require('ol.render.webgl.polygonreplay.shader.DefaultVertex'); -goog.require('ol.style.Stroke'); -goog.require('ol.structs.LinkedList'); -goog.require('ol.structs.RBush'); -goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); goog.require('ol.webgl.Context'); -/** - * @constructor - * @extends {ol.render.VectorContext} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @protected - * @struct - */ -ol.render.webgl.Replay = function(tolerance, maxExtent) { - ol.render.VectorContext.call(this); - - /** - * @protected - * @type {number} - */ - this.tolerance = tolerance; - - /** - * @protected - * @const - * @type {ol.Extent} - */ - this.maxExtent = maxExtent; - - /** - * @private - * @type {ol.Extent} - */ - this.bufferedMaxExtent_ = null; - - /** - * The origin of the coordinate system for the point coordinates sent to - * the GPU. To eliminate jitter caused by precision problems in the GPU - * we use the "Rendering Relative to Eye" technique described in the "3D - * Engine Design for Virtual Globes" book. - * @private - * @type {ol.Coordinate} - */ - this.origin_ = ol.extent.getCenter(maxExtent); - - /** - * @type {ol.Transform} - * @private - */ - this.projectionMatrix_ = ol.transform.create(); - - /** - * @type {ol.Transform} - * @private - */ - this.offsetRotateMatrix_ = ol.transform.create(); - - /** - * @type {ol.Transform} - * @private - */ - this.offsetScaleMatrix_ = ol.transform.create(); - - /** - * @type {Array.} - * @private - */ - this.tmpMat4_ = ol.vec.Mat4.create(); - - /** - * @type {Array.} - * @private - */ - this.indices_ = []; - - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.indicesBuffer_ = null; - - /** - * Start index per feature (the index). - * @type {Array.} - * @private - */ - this.startIndices_ = []; - - /** - * Start index per feature (the feature). - * @type {Array.} - * @private - */ - this.startIndicesFeature_ = []; - - /** - * @type {Array.} - * @private - */ - this.vertices_ = []; - - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.verticesBuffer_ = null; - - /** - * Optional parameter for PolygonReplay instances. - * @type {ol.render.webgl.LineStringReplay|undefined} - * @private - */ - this.lineStringReplay_ = undefined; - -}; -ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext); - - -ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = goog.abstractMethod; - -ol.render.webgl.Replay.prototype.finish = goog.abstractMethod; - -ol.render.webgl.Replay.prototype.setUpProgram_ = goog.abstractMethod; - -ol.render.webgl.Replay.prototype.drawReplay_ = goog.abstractMethod; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.Replay.prototype.drawHitDetectionReplay_ = function(gl, context, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent) { - if (!oneByOne) { - // draw all hit-detection features in "once" (by texture group) - return this.drawHitDetectionReplayAll_(gl, context, - skippedFeaturesHash, featureCallback); - } else { - // draw hit-detection features one by one - return this.drawHitDetectionReplayOneByOne_(gl, context, - skippedFeaturesHash, featureCallback, opt_hitExtent); - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.Replay.prototype.drawHitDetectionReplayAll_ = function(gl, context, skippedFeaturesHash, - featureCallback) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawReplay_(gl, context, skippedFeaturesHash, true); - - var result = featureCallback(null); - if (result) { - return result; - } else { - return undefined; - } -}; - - -ol.render.webgl.Replay.prototype.drawHitDetectionReplayOneByOne_ = goog.abstractMethod; - - -/** - * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @param {number} opacity Global opacity. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.Replay.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent) { - var gl = context.getGL(); - - // bind the vertices buffer - goog.asserts.assert(this.verticesBuffer_, - 'verticesBuffer must not be null'); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); - - // bind the indices buffer - goog.asserts.assert(this.indicesBuffer_, - 'indicesBuffer must not be null'); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - - var locations = this.setUpProgram_(gl, context, size, pixelRatio); - - // set the "uniform" values - var projectionMatrix = ol.transform.reset(this.projectionMatrix_); - ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1])); - ol.transform.rotate(projectionMatrix, -rotation); - ol.transform.translate(projectionMatrix, -(center[0] - this.origin_[0]), -(center[1] - this.origin_[1])); - - var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_); - ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]); - - var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_); - if (rotation !== 0) { - ol.transform.rotate(offsetRotateMatrix, -rotation); - } - - gl.uniformMatrix4fv(locations.u_projectionMatrix, false, - ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix)); - gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, - ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix)); - gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false, - ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix)); - gl.uniform1f(locations.u_opacity, opacity); - - // draw! - var result; - if (featureCallback === undefined) { - this.drawReplay_(gl, context, skippedFeaturesHash); - } else { - // draw feature by feature for the hit-detection - result = this.drawHitDetectionReplay_(gl, context, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent); - } - - // disable the vertex attrib arrays - for (var i in locations) { - if (typeof locations[i] === 'number') { - gl.disableVertexAttribArray(locations[i]); - } - } - - if (this.lineStringReplay_) { - this.lineStringReplay_.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent); - } - - return result; -}; - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {number} start Start index. - * @param {number} end End index. - */ -ol.render.webgl.Replay.prototype.drawElements_ = function( - gl, context, start, end) { - var elementType = context.hasOESElementIndexUint ? - ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; - var elementSize = context.hasOESElementIndexUint ? 4 : 2; - - var numItems = end - start; - var offsetInBytes = start * elementSize; - gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes); -}; /** * @constructor * @extends {ol.render.webgl.Replay} * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. - * @protected * @struct */ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { @@ -440,20 +139,19 @@ ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay); /** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. + * @inheritDoc */ ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) { // We only delete our stuff here. The shaders and the program may // be used by other ImageReplay instances (for other layers). And // they will be deleted when disposing of the ol.webgl.Context // object. - ol.DEBUG && console.assert(this.verticesBuffer_, + ol.DEBUG && console.assert(this.verticesBuffer, 'verticesBuffer must not be null'); - ol.DEBUG && console.assert(this.indicesBuffer_, + ol.DEBUG && console.assert(this.indicesBuffer, 'indicesBuffer must not be null'); - var verticesBuffer = this.verticesBuffer_; - var indicesBuffer = this.indicesBuffer_; + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; var textures = this.textures_; var hitDetectionTextures = this.hitDetectionTextures_; var gl = context.getGL(); @@ -510,12 +208,12 @@ ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinate var width = /** @type {number} */ (this.width_); var cos = Math.cos(rotation); var sin = Math.sin(rotation); - var numIndices = this.indices_.length; - var numVertices = this.vertices_.length; + var numIndices = this.indices.length; + var numVertices = this.vertices.length; var i, n, offsetX, offsetY, x, y; for (i = offset; i < end; i += stride) { - x = flatCoordinates[i] - this.origin_[0]; - y = flatCoordinates[i + 1] - this.origin_[1]; + x = flatCoordinates[i] - this.origin[0]; + y = flatCoordinates[i + 1] - this.origin[1]; // There are 4 vertices per [x, y] point, one for each corner of the // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if @@ -532,57 +230,57 @@ ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinate // bottom-left corner offsetX = -scale * anchorX; offsetY = -scale * (height - anchorY); - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = originX / imageWidth; - this.vertices_[numVertices++] = (originY + height) / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; // bottom-right corner offsetX = scale * (width - anchorX); offsetY = -scale * (height - anchorY); - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = (originX + width) / imageWidth; - this.vertices_[numVertices++] = (originY + height) / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; // top-right corner offsetX = scale * (width - anchorX); offsetY = scale * anchorY; - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = (originX + width) / imageWidth; - this.vertices_[numVertices++] = originY / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; // top-left corner offsetX = -scale * anchorX; offsetY = scale * anchorY; - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = originX / imageWidth; - this.vertices_[numVertices++] = originY / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n + 3; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 3; } return numVertices; @@ -593,8 +291,8 @@ ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinate * @inheritDoc */ ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) { - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); var flatCoordinates = multiPointGeometry.getFlatCoordinates(); var stride = multiPointGeometry.getStride(); this.drawCoordinates_( @@ -606,8 +304,8 @@ ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeomet * @inheritDoc */ ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) { - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); var flatCoordinates = pointGeometry.getFlatCoordinates(); var stride = pointGeometry.getStride(); this.drawCoordinates_( @@ -616,32 +314,30 @@ ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, featur /** - * @param {ol.webgl.Context} context Context. + * @inheritDoc */ ol.render.webgl.ImageReplay.prototype.finish = function(context) { var gl = context.getGL(); - this.groupIndices_.push(this.indices_.length); + this.groupIndices_.push(this.indices.length); ol.DEBUG && console.assert(this.images_.length === this.groupIndices_.length, 'number of images and groupIndices match'); - this.hitDetectionGroupIndices_.push(this.indices_.length); + this.hitDetectionGroupIndices_.push(this.indices.length); ol.DEBUG && console.assert(this.hitDetectionImages_.length === this.hitDetectionGroupIndices_.length, 'number of hitDetectionImages and hitDetectionGroupIndices match'); // create, bind, and populate the vertices buffer - this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); - var indices = this.indices_; + var indices = this.indices; var bits = context.hasOESElementIndexUint ? 32 : 16; ol.DEBUG && console.assert(indices[indices.length - 1] < Math.pow(2, bits), 'Too large element index detected [%s] (OES_element_index_uint "%s")', indices[indices.length - 1], context.hasOESElementIndexUint); // create, bind, and populate the indices buffer - this.indicesBuffer_ = new ol.webgl.Buffer(indices); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + this.indicesBuffer = new ol.webgl.Buffer(indices); // create textures /** @type {Object.} */ @@ -664,14 +360,14 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) { this.hitDetectionImages_ = null; this.imageHeight_ = undefined; this.imageWidth_ = undefined; - this.indices_ = null; + this.indices = null; this.opacity_ = undefined; this.originX_ = undefined; this.originY_ = undefined; this.rotateWithView_ = undefined; this.rotation_ = undefined; this.scale_ = undefined; - this.vertices_ = null; + this.vertices = null; this.width_ = undefined; }; @@ -707,14 +403,9 @@ ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, image /** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @return {ol.render.webgl.imagereplay.shader.Default.Locations} Locations. + * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context, size, pixelRatio) { +ol.render.webgl.ImageReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { // get the program var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment; var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex; @@ -757,15 +448,11 @@ ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context, size return locations; }; + /** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {boolean} hitDetection Hit detection mode. + * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { +ol.render.webgl.ImageReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { var textures = hitDetection ? this.hitDetectionTextures_ : this.textures_; var groupIndices = hitDetection ? this.hitDetectionGroupIndices_ : this.groupIndices_; ol.DEBUG && console.assert(textures.length === groupIndices.length, @@ -779,7 +466,7 @@ ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippe for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); var end = groupIndices[i]; - this.drawElements_(gl, context, start, end); + this.drawElements(gl, context, start, end); start = end; } } @@ -824,25 +511,25 @@ ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context var start = groupStart; var end = groupStart; - while (featureIndex < this.startIndices_.length && - this.startIndices_[featureIndex] <= groupEnd) { - var feature = this.startIndicesFeature_[featureIndex]; + while (featureIndex < this.startIndices.length && + this.startIndices[featureIndex] <= groupEnd) { + var feature = this.startIndicesFeature[featureIndex]; var featureUid = ol.getUid(feature).toString(); if (skippedFeaturesHash[featureUid] !== undefined) { // feature should be skipped if (start !== end) { // draw the features so far - this.drawElements_(gl, context, start, end); + this.drawElements(gl, context, start, end); } // continue with the next feature - start = (featureIndex === this.startIndices_.length - 1) ? - groupEnd : this.startIndices_[featureIndex + 1]; + start = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; end = start; } else { // the feature is not skipped, augment the end index - end = (featureIndex === this.startIndices_.length - 1) ? - groupEnd : this.startIndices_[featureIndex + 1]; + end = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; } featureIndex++; } @@ -850,81 +537,23 @@ ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context if (start !== end) { // draw the remaining features (in case there was no skipped feature // in this texture group, all features of a group are drawn together) - this.drawElements_(gl, context, start, end); + this.drawElements(gl, context, start, end); } } }; /** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T + * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = function(gl, context, skippedFeaturesHash, featureCallback, oneByOne, - opt_hitExtent) { - if (!oneByOne) { - // draw all hit-detection features in "once" (by texture group) - return this.drawHitDetectionReplayAll_(gl, context, - skippedFeaturesHash, featureCallback); - } else { - // draw hit-detection features one by one - return this.drawHitDetectionReplayOneByOne_(gl, context, - skippedFeaturesHash, featureCallback, opt_hitExtent); - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayAll_ = function(gl, context, skippedFeaturesHash, featureCallback) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawReplay_(gl, context, skippedFeaturesHash, true); - - var result = featureCallback(null); - if (result) { - return result; - } else { - return undefined; - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, featureCallback, opt_hitExtent) { ol.DEBUG && console.assert(this.hitDetectionTextures_.length === this.hitDetectionGroupIndices_.length, 'number of hitDetectionTextures and hitDetectionGroupIndices match'); var i, groupStart, start, end, feature, featureUid; - var featureIndex = this.startIndices_.length - 1; + var featureIndex = this.startIndices.length - 1; for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { gl.bindTexture(ol.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0; @@ -932,9 +561,9 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function // draw all features for this texture group while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - start = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; featureUid = ol.getUid(feature).toString(); if (skippedFeaturesHash[featureUid] === undefined && @@ -943,7 +572,7 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function /** @type {Array} */ (opt_hitExtent), feature.getGeometry().getExtent()))) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements_(gl, context, start, end); + this.drawElements(gl, context, start, end); var result = featureCallback(feature); if (result) { @@ -959,13 +588,6 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function }; -/** - * @inheritDoc - * @abstract - */ -ol.render.webgl.ImageReplay.prototype.setFillStrokeStyle = function() {}; - - /** * @inheritDoc */ @@ -1003,7 +625,7 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { } else { currentImage = this.images_[this.images_.length - 1]; if (ol.getUid(currentImage) != ol.getUid(image)) { - this.groupIndices_.push(this.indices_.length); + this.groupIndices_.push(this.indices.length); ol.DEBUG && console.assert(this.groupIndices_.length === this.images_.length, 'number of groupIndices and images match'); this.images_.push(image); @@ -1016,7 +638,7 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { currentImage = this.hitDetectionImages_[this.hitDetectionImages_.length - 1]; if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) { - this.hitDetectionGroupIndices_.push(this.indices_.length); + this.hitDetectionGroupIndices_.push(this.indices.length); ol.DEBUG && console.assert(this.hitDetectionGroupIndices_.length === this.hitDetectionImages_.length, 'number of hitDetectionGroupIndices and hitDetectionImages match'); @@ -1037,1938 +659,3 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { this.scale_ = scale; this.width_ = size[0]; }; - - -/** - * @constructor - * @extends {ol.render.webgl.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @protected - * @struct - */ -ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - /** - * @private - * @type {ol.render.webgl.linestringreplay.shader.Default.Locations} - */ - this.defaultLocations_ = null; - - /** - * @private - * @type {Array.>} - */ - this.styles_ = []; - - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; - - /** - * @private - * @type {{strokeColor: (Array.|null), - * lineCap: (string|undefined), - * lineDash: Array., - * lineJoin: (string|undefined), - * lineWidth: (number|undefined), - * miterLimit: (number|undefined), - * changed: boolean}|null} - */ - this.state_ = { - strokeColor: null, - lineCap: undefined, - lineDash: null, - lineJoin: undefined, - lineWidth: undefined, - miterLimit: undefined, - changed: false - }; - -}; -ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); - - -/** - * Draw one segment. - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @private - */ -ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { - - var i, ii; - var numVertices = this.vertices_.length; - var numIndices = this.indices_.length; - //To save a vertex, the direction of a point is a product of the sign (1 or -1), a prime from - //ol.render.webgl.lineStringInstruction, and a rounding factor (1 or 2). If the product is even, - //we round it. If it is odd, we don't. - var lineJoin = this.state_.lineJoin === 'bevel' ? 0 : - this.state_.lineJoin === 'miter' ? 1 : 2; - var lineCap = this.state_.lineCap === 'butt' ? 0 : - this.state_.lineCap === 'square' ? 1 : 2; - var closed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, offset, end, stride); - var startCoords, sign, n; - var lastIndex = numIndices; - var lastSign = 1; - //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. - //We rotate those points, thus every point is RTE corrected only once. - var p0, p1, p2; - - for (i = offset, ii = end; i < ii; i += stride) { - - n = numVertices / 7; - - p0 = p1; - p1 = p2 || [flatCoordinates[i], flatCoordinates[i + 1]]; - //First vertex. - if (i === offset) { - p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; - if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { - break; - } - if (closed) { - //A closed line! Complete the circle. - p0 = [flatCoordinates[end - stride * 2], - flatCoordinates[end - stride * 2 + 1]]; - - startCoords = p2; - } else { - //Add the first two/four vertices. - - if (lineCap) { - numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); - - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n + 2; - - } - - numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - - lastIndex = numVertices / 7 - 1; - - continue; - } - } else if (i === end - stride) { - //Last vertex. - if (closed) { - //Same as the first vertex. - p2 = startCoords; - break; - } else { - //For the compiler not to complain. This will never be [0, 0]. - p0 = p0 || [0, 0]; - - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); - - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastIndex - 1; - this.indices_[numIndices++] = lastIndex; - - this.indices_[numIndices++] = lastIndex; - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n; - - if (lineCap) { - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); - - numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n + 2; - - } - - break; - } - } else { - p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; - } - - sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) - ? -1 : 1; - - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.lineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); - - if (i > offset) { - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastIndex - 1; - this.indices_[numIndices++] = lastIndex; - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; - } - - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n + 1; - - lastIndex = n + 2; - lastSign = sign; - - //Add miter - if (lineJoin) { - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.lineStringInstruction.MITER_TOP * lineJoin, numVertices); - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n; - } - } - - if (closed) { - //Link the last triangle/rhombus to the first one. - //n will never be numVertices / 7 here. However, the compiler complains otherwise. - n = n || numVertices / 7; - sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) - ? 1 : -1; - - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); - - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastIndex - 1; - this.indices_[numIndices++] = lastIndex; - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; - } -}; - -/** - * @param {Array.} p0 Last coordinates. - * @param {Array.} p1 Current coordinates. - * @param {Array.} p2 Next coordinates. - * @param {number} product Sign, instruction, and rounding product. - * @param {number} numVertices Vertex counter. - * @return {number} Vertex counter. - * @private - */ -ol.render.webgl.LineStringReplay.prototype.addVertices_ = function(p0, p1, p2, product, numVertices) { - this.vertices_[numVertices++] = p0[0]; - this.vertices_[numVertices++] = p0[1]; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = p2[0]; - this.vertices_[numVertices++] = p2[1]; - this.vertices_[numVertices++] = product; - - return numVertices; -}; - -/** - * Check if the linestring can be drawn (i. e. valid). - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {boolean} The linestring can be drawn. - * @private - */ -ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, offset, end, stride) { - var range = end - offset; - if (range < stride * 2) { - return false; - } else if (range === stride * 2) { - var firstP = [flatCoordinates[offset], flatCoordinates[offset + 1]]; - var lastP = [flatCoordinates[offset + stride], flatCoordinates[offset + stride + 1]]; - return !ol.array.equals(firstP, lastP); - } - - return true; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { - var flatCoordinates = lineStringGeometry.getFlatCoordinates(); - var stride = lineStringGeometry.getStride(); - if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { - flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, - stride, -this.origin_[0], -this.origin_[1]); - if (this.state_.changed) { - this.styleIndices_.push(this.indices_.length); - this.state_.changed = false; - } - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); - } -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { - var indexCount = this.indices_.length; - var lineStringGeometries = multiLineStringGeometry.getLineStrings(); - var i, ii; - for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { - var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); - var stride = lineStringGeometries[i].getStride(); - if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { - flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, - stride, -this.origin_[0], -this.origin_[1]); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); - } - } - if (this.indices_.length > indexCount) { - this.startIndices_.push(indexCount); - this.startIndicesFeature_.push(feature); - if (this.state_.changed) { - this.styleIndices_.push(indexCount); - this.state_.changed = false; - } - } -}; - - -/** - * @param {ol.webgl.Context} context Context. - **/ -ol.render.webgl.LineStringReplay.prototype.finish = function(context) { - // create, bind, and populate the vertices buffer - this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); - - // create, bind, and populate the indices buffer - this.indicesBuffer_ = new ol.webgl.Buffer(this.indices_); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - - this.startIndices_.push(this.indices_.length); - - //Clean up, if there is nothing to draw - if (this.styleIndices_.length === 0 && this.styles_.length > 0) { - this.styles_ = []; - } - - this.vertices_ = null; - this.indices_ = null; -}; - - -/** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. - */ -ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) { - // We only delete our stuff here. The shaders and the program may - // be used by other LineStringReplay instances (for other layers). And - // they will be deleted when disposing of the ol.webgl.Context - // object. - goog.asserts.assert(this.verticesBuffer_, 'verticesBuffer must not be null'); - var verticesBuffer = this.verticesBuffer_; - var indicesBuffer = this.indicesBuffer_; - return function() { - context.deleteBuffer(verticesBuffer); - context.deleteBuffer(indicesBuffer); - }; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @return {ol.render.webgl.linestringreplay.shader.Default.Locations} Locations. - */ -ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, size, pixelRatio) { - // get the program - var fragmentShader, vertexShader; - fragmentShader = - ol.render.webgl.linestringreplay.shader.DefaultFragment.getInstance(); - vertexShader = - ol.render.webgl.linestringreplay.shader.DefaultVertex.getInstance(); - var program = context.getProgram(fragmentShader, vertexShader); - - // get the locations - var locations; - if (!this.defaultLocations_) { - locations = new ol.render.webgl.linestringreplay.shader.Default - .Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - context.useProgram(program); - - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_lastPos); - gl.vertexAttribPointer(locations.a_lastPos, 2, ol.webgl.FLOAT, - false, 28, 0); - - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 28, 8); - - gl.enableVertexAttribArray(locations.a_nextPos); - gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT, - false, 28, 16); - - gl.enableVertexAttribArray(locations.a_direction); - gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT, - false, 28, 24); - - // Enable renderer specific uniforms. If clauses needed, as otherwise the compiler complains. - gl.uniform2fv(locations.u_size, size); - gl.uniform1f(locations.u_pixelRatio, pixelRatio); - - return locations; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {boolean} hitDetection Hit detection mode. - */ -ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { - //Save GL parameters. - var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); - var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); - - if (!hitDetection) { - gl.enable(gl.DEPTH_TEST); - gl.depthMask(true); - gl.depthFunc(gl.NOTEQUAL); - } - - if (!ol.object.isEmpty(skippedFeaturesHash)) { - this.drawReplaySkipping_(gl, context, skippedFeaturesHash); - } else { - goog.asserts.assert(this.styles_.length === this.styleIndices_.length, - 'number of styles and styleIndices match'); - - //Draw by style groups to minimize drawElements() calls. - var i, start, end, nextStyle; - end = this.startIndices_[this.startIndices_.length - 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - start = this.styleIndices_[i]; - nextStyle = this.styles_[i]; - this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - this.drawElements_(gl, context, start, end); - end = start; - } - } - if (!hitDetection) { - gl.clear(gl.DEPTH_BUFFER_BIT); - gl.disable(gl.DEPTH_TEST); - //Restore GL parameters. - gl.depthMask(tmpDepthMask); - gl.depthFunc(tmpDepthFunc); - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object} skippedFeaturesHash Ids of features to skip. - */ -ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { - goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, - 'number of startIndices and startIndicesFeature match'); - - var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; - featureIndex = this.startIndices_.length - 2; - end = start = this.startIndices_[featureIndex + 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - nextStyle = this.styles_[i]; - this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - groupStart = this.styleIndices_[i]; - - while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - featureStart = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; - featureUid = goog.getUid(feature).toString(); - - if (skippedFeaturesHash[featureUid]) { - if (start !== end) { - this.drawElements_(gl, context, start, end); - } - end = featureStart; - } - featureIndex--; - start = featureStart; - } - if (start !== end) { - this.drawElements_(gl, context, start, end); - } - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - goog.asserts.assert(this.styles_.length === this.styleIndices_.length, - 'number of styles and styleIndices match'); - goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, - 'number of startIndices and startIndicesFeature match'); - - var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; - featureIndex = this.startIndices_.length - 2; - end = this.startIndices_[featureIndex + 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - nextStyle = this.styles_[i]; - this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - groupStart = this.styleIndices_[i]; - - while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - start = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; - featureUid = goog.getUid(feature).toString(); - - if (skippedFeaturesHash[featureUid] === undefined && - feature.getGeometry() && - (opt_hitExtent === undefined || ol.extent.intersects( - /** @type {Array} */ (opt_hitExtent), - feature.getGeometry().getExtent()))) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements_(gl, context, start, end); - - var result = featureCallback(feature); - - if (result) { - return result; - } - - } - featureIndex--; - end = start; - } - } - return undefined; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {Array.} color Color. - * @param {number} lineWidth Line width. - * @param {number} miterLimit Miter limit. - */ -ol.render.webgl.LineStringReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth, miterLimit) { - gl.uniform4fv(this.defaultLocations_.u_color, color); - gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); - gl.uniform1f(this.defaultLocations_.u_miterLimit, miterLimit); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - goog.asserts.assert(this.state_, 'this.state_ should not be null'); - goog.asserts.assert(!fillStyle, 'fillStyle should be null'); - goog.asserts.assert(strokeStyle, 'strokeStyle should not be null'); - var strokeStyleLineCap = strokeStyle.getLineCap(); - this.state_.lineCap = strokeStyleLineCap !== undefined ? - strokeStyleLineCap : ol.render.webgl.defaultLineCap; - var strokeStyleLineDash = strokeStyle.getLineDash(); - this.state_.lineDash = strokeStyleLineDash ? - strokeStyleLineDash : ol.render.webgl.defaultLineDash; - var strokeStyleLineJoin = strokeStyle.getLineJoin(); - this.state_.lineJoin = strokeStyleLineJoin !== undefined ? - strokeStyleLineJoin : ol.render.webgl.defaultLineJoin; - var strokeStyleColor = ol.color.asArray(strokeStyle.getColor()).map(function(c, i) { - return i != 3 ? c / 255 : c; - }) || ol.render.webgl.defaultStrokeStyle; - var strokeStyleWidth = strokeStyle.getWidth(); - strokeStyleWidth = strokeStyleWidth !== undefined ? - strokeStyleWidth : ol.render.webgl.defaultLineWidth; - var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); - strokeStyleMiterLimit = strokeStyleMiterLimit !== undefined ? - strokeStyleMiterLimit : ol.render.webgl.defaultMiterLimit; - if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || - this.state_.lineWidth !== strokeStyleWidth || this.state_.miterLimit !== strokeStyleMiterLimit) { - this.state_.changed = true; - this.state_.strokeColor = strokeStyleColor; - this.state_.lineWidth = strokeStyleWidth; - this.state_.miterLimit = strokeStyleMiterLimit; - this.styles_.push([strokeStyleColor, strokeStyleWidth, strokeStyleMiterLimit]); - } -}; - - -/** - * @constructor - * @extends {ol.render.webgl.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @protected - * @struct - */ -ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - this.lineStringReplay_ = new ol.render.webgl.LineStringReplay( - tolerance, maxExtent); - - /** - * @private - * @type {ol.render.webgl.polygonreplay.shader.Default.Locations} - */ - this.defaultLocations_ = null; - - /** - * @private - * @type {Array.>} - */ - this.styles_ = []; - - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; - - /** - * @private - * @type {{fillColor: (Array.|null), - * changed: boolean}|null} - */ - this.state_ = { - fillColor: null, - changed: false - }; - -}; -ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); - - -/** - * Draw one polygon. - * @param {Array.} flatCoordinates Flat coordinates. - * @param {Array.>} holeFlatCoordinates Hole flat coordinates. - * @param {number} stride Stride. - * @private - */ -ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( - flatCoordinates, holeFlatCoordinates, stride) { - // Triangulate the polygon - var outerRing = new ol.structs.LinkedList(); - var rtree = new ol.structs.RBush(); - // Initialize the outer ring - var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true); - - // Eliminate holes, if there are any - if (holeFlatCoordinates.length) { - var i, ii; - var holeLists = []; - for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { - var holeList = { - list: new ol.structs.LinkedList(), - maxX: undefined - }; - holeLists.push(holeList); - holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i], - stride, holeList.list, rtree, false); - } - holeLists.sort(function(a, b) { - return b.maxX - a.maxX; - }); - for (i = 0; i < holeLists.length; ++i) { - this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX, rtree); - } - } - this.classifyPoints_(outerRing, rtree, false); - this.triangulate_(outerRing, rtree); -}; - - -/** - * Inserts flat coordinates in a linked list and adds them to the vertex buffer. - * @private - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} stride Stride. - * @param {ol.structs.LinkedList} list Linked list. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean} clockwise Coordinate order should be clockwise. - * @return {number} Maximum X value. - */ -ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( - flatCoordinates, stride, list, rtree, clockwise) { - var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, - 0, flatCoordinates.length, stride); - var i, ii, maxX; - var n = this.vertices_.length / 2; - /** @type {ol.WebglPolygonVertex} */ - var start; - /** @type {ol.WebglPolygonVertex} */ - var p0; - /** @type {ol.WebglPolygonVertex} */ - var p1; - var extents = []; - var segments = []; - if (clockwise === isClockwise) { - start = this.createPoint_(flatCoordinates[0], flatCoordinates[1], n++); - p0 = start; - maxX = flatCoordinates[0]; - for (i = stride, ii = flatCoordinates.length; i < ii; i += stride) { - p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); - segments.push(this.insertItem_(p0, p1, list)); - extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); - maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; - p0 = p1; - } - segments.push(this.insertItem_(p1, start, list)); - extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); - } else { - var end = flatCoordinates.length - stride; - start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++); - p0 = start; - maxX = flatCoordinates[end]; - for (i = end - stride, ii = 0; i >= ii; i -= stride) { - p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); - segments.push(this.insertItem_(p0, p1, list)); - extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); - maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; - p0 = p1; - } - segments.push(this.insertItem_(p1, start, list)); - extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); - } - rtree.load(extents, segments); - - return maxX; -}; - - -/** - * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices. - * @private - * @param {ol.structs.LinkedList} list Polygon ring. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean} ccw The orientation of the polygon is counter-clockwise. - * @return {boolean} There were reclassified points. - */ -ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) { - var start = list.firstItem(); - var s0 = start; - var s1 = list.nextItem(); - var pointsReclassified = false; - do { - var reflex = ccw ? ol.render.webgl.triangleIsCounterClockwise(s1.p1.x, - s1.p1.y, s0.p1.x, s0.p1.y, s0.p0.x, s0.p0.y) : - ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x, - s0.p1.y, s1.p1.x, s1.p1.y); - if (reflex === undefined) { - this.removeItem_(s0, s1, list, rtree); - pointsReclassified = true; - if (s1 === start) { - start = list.getNextItem(); - } - s1 = s0; - list.prevItem(); - } else if (s0.p1.reflex !== reflex) { - s0.p1.reflex = reflex; - pointsReclassified = true; - } - s0 = s1; - s1 = list.nextItem(); - } while (s0 !== start); - return pointsReclassified; -}; - - -/** - * @private - * @param {ol.structs.LinkedList} hole Linked list of the hole. - * @param {number} holeMaxX Maximum X value of the hole. - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {number} listMaxX Maximum X value of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, - list, listMaxX, rtree) { - var seg = hole.firstItem(); - while (seg.p1.x !== holeMaxX) { - seg = hole.nextItem(); - } - - var p1 = seg.p1; - /** @type {ol.WebglPolygonVertex} */ - var p2 = {x: listMaxX, y: p1.y, i: -1}; - var minDist = Infinity; - var i, ii, bestPoint; - /** @type {ol.WebglPolygonVertex} */ - var p5; - - var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); - for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { - var currSeg = intersectingSegments[i]; - if (currSeg.p0 !== p1 && currSeg.p1 !== p1) { - var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, - currSeg.p1, true); - var dist = Math.abs(p1.x - intersection[0]); - if (dist < minDist) { - minDist = dist; - p5 = {x: intersection[0], y: intersection[1], i: -1}; - seg = currSeg; - } - } - } - bestPoint = seg.p1; - - var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree); - if (pointsInTriangle.length) { - var theta = Infinity; - for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) { - var currPoint = pointsInTriangle[i]; - var currTheta = Math.atan2(p1.y - currPoint.y, p2.x - currPoint.x); - if (currTheta < theta || (currTheta === theta && currPoint.x < bestPoint.x)) { - theta = currTheta; - bestPoint = currPoint; - } - } - } - - seg = list.firstItem(); - while (seg.p1 !== bestPoint) { - seg = list.nextItem(); - } - - //We clone the bridge points as they can have different convexity. - var p0Bridge = {x: p1.x, y: p1.y, i: p1.i, reflex: undefined}; - var p1Bridge = {x: seg.p1.x, y: seg.p1.y, i: seg.p1.i, reflex: undefined}; - - hole.getNextItem().p0 = p0Bridge; - this.insertItem_(p1, seg.p1, hole, rtree); - this.insertItem_(p1Bridge, p0Bridge, hole, rtree); - seg.p1 = p1Bridge; - hole.setFirstItem(); - list.concat(hole); -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { - var ccw = false; - var simple = this.isSimple_(list, rtree); - - // Start clipping ears - while (list.getLength() > 3) { - if (simple) { - if (!this.clipEars_(list, rtree, simple, ccw)) { - if (!this.classifyPoints_(list, rtree, ccw)) { - // Due to the behavior of OL's PIP algorithm, the ear clipping cannot - // introduce touching segments. However, the original data may have some. - if (!this.resolveLocalSelfIntersections_(list, rtree, true)) { - // Something went wrong. - break; - } - } - } - } else { - if (!this.clipEars_(list, rtree, simple, ccw)) { - // We ran out of ears, try to reclassify. - if (!this.classifyPoints_(list, rtree, ccw)) { - // We have a bad polygon, try to resolve local self-intersections. - if (!this.resolveLocalSelfIntersections_(list, rtree)) { - simple = this.isSimple_(list, rtree); - if (!simple) { - // We have a really bad polygon, try more time consuming methods. - this.splitPolygon_(list, rtree); - break; - } else { - ccw = !this.isClockwise_(list); - this.classifyPoints_(list, rtree, ccw); - } - } - } - } - } - } - if (list.getLength() === 3) { - var numIndices = this.indices_.length; - this.indices_[numIndices++] = list.getPrevItem().p0.i; - this.indices_[numIndices++] = list.getCurrItem().p0.i; - this.indices_[numIndices++] = list.getNextItem().p0.i; - } -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean} simple The polygon is simple. - * @param {boolean} ccw Orientation of the polygon is counter-clockwise. - * @return {boolean} There were processed ears. - */ -ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple, ccw) { - var numIndices = this.indices_.length; - var start = list.firstItem(); - var s0 = list.getPrevItem(); - var s1 = start; - var s2 = list.nextItem(); - var s3 = list.getNextItem(); - var p0, p1, p2; - var processedEars = false; - do { - p0 = s1.p0; - p1 = s1.p1; - p2 = s2.p1; - if (p1.reflex === false) { - // We might have a valid ear - var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0, - s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1); - if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) && - diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) { - //The diagonal is completely inside the polygon - if (simple || p0.reflex === false || p2.reflex === false || - ol.geom.flat.orient.linearRingIsClockwise([s0.p0.x, s0.p0.y, p0.x, - p0.y, p1.x, p1.y, p2.x, p2.y, s3.p1.x, s3.p1.y], 0, 10, 2) === !ccw) { - //The diagonal is persumably valid, we have an ear - this.indices_[numIndices++] = p0.i; - this.indices_[numIndices++] = p1.i; - this.indices_[numIndices++] = p2.i; - this.removeItem_(s1, s2, list, rtree); - if (s2 === start) { - start = s3; - } - processedEars = true; - } - } - } - // Else we have a reflex point. - s0 = list.getPrevItem(); - s1 = list.getCurrItem(); - s2 = list.nextItem(); - s3 = list.getNextItem(); - } while (s1 !== start && list.getLength() > 3); - - return processedEars; -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean=} opt_touch Resolve touching segments. - * @return {boolean} There were resolved intersections. -*/ -ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function( - list, rtree, opt_touch) { - var start = list.firstItem(); - list.nextItem(); - var s0 = start; - var s1 = list.nextItem(); - var resolvedIntersections = false; - - do { - var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1, - opt_touch); - if (intersection) { - var breakCond = false; - var numVertices = this.vertices_.length; - var numIndices = this.indices_.length; - var n = numVertices / 2; - var seg = list.prevItem(); - list.removeItem(); - rtree.remove(seg); - breakCond = (seg === start); - var p; - if (opt_touch) { - if (intersection[0] === s0.p0.x && intersection[1] === s0.p0.y) { - list.prevItem(); - p = s0.p0; - s1.p0 = p; - rtree.remove(s0); - breakCond = breakCond || (s0 === start); - } else { - p = s1.p1; - s0.p1 = p; - rtree.remove(s1); - breakCond = breakCond || (s1 === start); - } - list.removeItem(); - } else { - p = this.createPoint_(intersection[0], intersection[1], n); - s0.p1 = p; - s1.p0 = p; - rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), - Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); - rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y), - Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1); - } - - this.indices_[numIndices++] = seg.p0.i; - this.indices_[numIndices++] = seg.p1.i; - this.indices_[numIndices++] = p.i; - - resolvedIntersections = true; - if (breakCond) { - break; - } - } - - s0 = list.getPrevItem(); - s1 = list.nextItem(); - } while (s0 !== start); - return resolvedIntersections; -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @return {boolean} The polygon is simple. - */ -ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) { - var start = list.firstItem(); - var seg = start; - do { - if (this.getIntersections_(seg, rtree).length) { - return false; - } - seg = list.nextItem(); - } while (seg !== start); - return true; -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @return {boolean} Orientation is clockwise. - */ -ol.render.webgl.PolygonReplay.prototype.isClockwise_ = function(list) { - var length = list.getLength() * 2; - var flatCoordinates = new Array(length); - var start = list.firstItem(); - var seg = start; - var i = 0; - do { - flatCoordinates[i++] = seg.p0.x; - flatCoordinates[i++] = seg.p0.y; - seg = list.nextItem(); - } while (seg !== start); - return ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, 0, length, 2); -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) { - var start = list.firstItem(); - var s0 = start; - do { - var intersections = this.getIntersections_(s0, rtree); - if (intersections.length) { - var s1 = intersections[0]; - var n = this.vertices_.length / 2; - var intersection = this.calculateIntersection_(s0.p0, - s0.p1, s1.p0, s1.p1); - var p = this.createPoint_(intersection[0], intersection[1], n); - var newPolygon = new ol.structs.LinkedList(); - var newRtree = new ol.structs.RBush(); - this.insertItem_(p, s0.p1, newPolygon, newRtree); - s0.p1 = p; - rtree.update([Math.min(s0.p0.x, p.x), Math.min(s0.p0.y, p.y), - Math.max(s0.p0.x, p.x), Math.max(s0.p0.y, p.y)], s0); - var currItem = list.nextItem(); - while (currItem !== s1) { - this.insertItem_(currItem.p0, currItem.p1, newPolygon, newRtree); - rtree.remove(currItem); - list.removeItem(); - currItem = list.getCurrItem(); - } - this.insertItem_(s1.p0, p, newPolygon, newRtree); - s1.p0 = p; - rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y), - Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1); - this.classifyPoints_(list, rtree, false); - this.triangulate_(list, rtree); - this.classifyPoints_(newPolygon, newRtree, false); - this.triangulate_(newPolygon, newRtree); - break; - } - s0 = list.nextItem(); - } while (s0 !== start); -}; - - -/** - * @private - * @param {number} x X coordinate. - * @param {number} y Y coordinate. - * @param {number} i Index. - * @return {ol.WebglPolygonVertex} List item. - */ -ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { - var numVertices = this.vertices_.length; - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - /** @type {ol.WebglPolygonVertex} */ - var p = { - x: x, - y: y, - i: i, - reflex: undefined - }; - return p; -}; - - -/** - * @private - * @param {ol.WebglPolygonVertex} p0 First point of segment. - * @param {ol.WebglPolygonVertex} p1 Second point of segment. - * @param {ol.structs.LinkedList} list Polygon ring. - * @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree. - * @return {ol.WebglPolygonSegment} segment. - */ -ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) { - var seg = { - p0: p0, - p1: p1 - }; - list.insertItem(seg); - if (opt_rtree) { - opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), - Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg); - } - return seg; -}; - - - /** - * @private - * @param {ol.WebglPolygonSegment} s0 Segment before the remove candidate. - * @param {ol.WebglPolygonSegment} s1 Remove candidate segment. - * @param {ol.structs.LinkedList} list Polygon ring. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) { - list.removeItem(); - s0.p1 = s1.p1; - rtree.remove(s1); - rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), - Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); -}; - - -/** - * @private - * @param {ol.WebglPolygonVertex} p0 First point. - * @param {ol.WebglPolygonVertex} p1 Second point. - * @param {ol.WebglPolygonVertex} p2 Third point. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean=} opt_reflex Only include reflex points. - * @return {Array.} Points in the triangle. - */ -ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, - p2, rtree, opt_reflex) { - var i, ii, j, p; - var result = []; - var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x, p2.x), - Math.min(p0.y, p1.y, p2.y), Math.max(p0.x, p1.x, p2.x), Math.max(p0.y, - p1.y, p2.y)]); - for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { - for (j in segmentsInExtent[i]) { - p = segmentsInExtent[i][j]; - if (p.x && p.y && (!opt_reflex || p.reflex)) { - if ((p.x !== p0.x || p.y !== p0.y) && (p.x !== p1.x || p.y !== p1.y) && - (p.x !== p2.x || p.y !== p2.y) && result.indexOf(p) === -1 && - ol.geom.flat.contains.linearRingContainsXY([p0.x, p0.y, p1.x, p1.y, - p2.x, p2.y], 0, 6, 2, p.x, p.y)) { - result.push(p); - } - } - } - } - return result; -}; - - -/** - * @private - * @param {ol.WebglPolygonSegment} segment Segment. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean=} opt_touch Touching segments should be considered an intersection. - * @return {Array.} Intersecting segments. - */ -ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) { - var p0 = segment.p0; - var p1 = segment.p1; - var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x), - Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]); - var result = []; - var i, ii; - for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { - var currSeg = segmentsInExtent[i]; - if (segment !== currSeg && (opt_touch || currSeg.p0 !== p1 || currSeg.p1 !== p0) && - this.calculateIntersection_(p0, p1, currSeg.p0, currSeg.p1, opt_touch)) { - result.push(currSeg); - } - } - return result; -}; - - -/** - * Line intersection algorithm by Paul Bourke. - * @see http://paulbourke.net/geometry/pointlineplane/ - * - * @private - * @param {ol.WebglPolygonVertex} p0 First point. - * @param {ol.WebglPolygonVertex} p1 Second point. - * @param {ol.WebglPolygonVertex} p2 Third point. - * @param {ol.WebglPolygonVertex} p3 Fourth point. - * @param {boolean=} opt_touch Touching segments should be considered an intersection. - * @return {Array.|undefined} Intersection coordinates. - */ -ol.render.webgl.PolygonReplay.prototype.calculateIntersection_ = function(p0, - p1, p2, p3, opt_touch) { - var denom = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y); - if (denom !== 0) { - var ua = ((p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)) / denom; - var ub = ((p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)) / denom; - if ((!opt_touch && ua > ol.render.webgl.EPSILON && ua < 1 - ol.render.webgl.EPSILON && - ub > ol.render.webgl.EPSILON && ub < 1 - ol.render.webgl.EPSILON) || (opt_touch && - ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)) { - return [p0.x + ua * (p1.x - p0.x), p0.y + ua * (p1.y - p0.y)]; - } - } - return undefined; -}; - - -/** - * @private - * @param {ol.WebglPolygonVertex} p0 Point before the start of the diagonal. - * @param {ol.WebglPolygonVertex} p1 Start point of the diagonal. - * @param {ol.WebglPolygonVertex} p2 Ear candidate. - * @param {ol.WebglPolygonVertex} p3 End point of the diagonal. - * @param {ol.WebglPolygonVertex} p4 Point after the end of the diagonal. - * @return {boolean} Diagonal is inside the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.diagonalIsInside_ = function(p0, p1, p2, p3, p4) { - if (p1.reflex === undefined || p3.reflex === undefined) { - return false; - } - var p1IsLeftOf = (p2.x - p3.x) * (p1.y - p3.y) > (p2.y - p3.y) * (p1.x - p3.x); - var p1IsRightOf = (p4.x - p3.x) * (p1.y - p3.y) < (p4.y - p3.y) * (p1.x - p3.x); - var p3IsLeftOf = (p0.x - p1.x) * (p3.y - p1.y) > (p0.y - p1.y) * (p3.x - p1.x); - var p3IsRightOf = (p2.x - p1.x) * (p3.y - p1.y) < (p2.y - p1.y) * (p3.x - p1.x); - var p1InCone = p3.reflex ? p1IsRightOf || p1IsLeftOf : p1IsRightOf && p1IsLeftOf; - var p3InCone = p1.reflex ? p3IsRightOf || p3IsLeftOf : p3IsRightOf && p3IsLeftOf; - return p1InCone && p3InCone; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { - var polygons = multiPolygonGeometry.getPolygons(); - var stride = multiPolygonGeometry.getStride(); - var currIndex = this.indices_.length; - var currLineIndex = this.lineStringReplay_.indices_.length; - var i, ii, j, jj; - for (i = 0, ii = polygons.length; i < ii; ++i) { - var linearRings = polygons[i].getLinearRings(); - if (linearRings.length > 0) { - var flatCoordinates = linearRings[0].getFlatCoordinates(); - flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, - stride, -this.origin_[0], -this.origin_[1]); - this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); - var holes = []; - var holeFlatCoords; - for (j = 1, jj = linearRings.length; j < jj; ++j) { - holeFlatCoords = linearRings[j].getFlatCoordinates(); - holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, - stride, -this.origin_[0], -this.origin_[1]); - holes.push(holeFlatCoords); - this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); - } - this.drawCoordinates_(flatCoordinates, holes, stride); - } - } - if (this.indices_.length > currIndex) { - this.startIndices_.push(currIndex); - this.startIndicesFeature_.push(feature); - if (this.state_.changed) { - this.styleIndices_.push(currIndex); - this.state_.changed = false; - } - } - if (this.lineStringReplay_.indices_.length > currLineIndex) { - this.lineStringReplay_.startIndices_.push(currLineIndex); - this.lineStringReplay_.startIndicesFeature_.push(feature); - if (this.lineStringReplay_.state_.changed) { - this.lineStringReplay_.styleIndices_.push(currLineIndex); - this.lineStringReplay_.state_.changed = false; - } - } -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { - var linearRings = polygonGeometry.getLinearRings(); - var stride = polygonGeometry.getStride(); - if (linearRings.length > 0) { - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); - if (this.state_.changed) { - this.styleIndices_.push(this.indices_.length); - this.state_.changed = false; - } - - this.lineStringReplay_.startIndices_.push(this.lineStringReplay_.indices_.length); - this.lineStringReplay_.startIndicesFeature_.push(feature); - if (this.lineStringReplay_.state_.changed) { - this.lineStringReplay_.styleIndices_.push(this.lineStringReplay_.indices_.length); - this.lineStringReplay_.state_.changed = false; - } - - var flatCoordinates = linearRings[0].getFlatCoordinates(); - flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, - stride, -this.origin_[0], -this.origin_[1]); - this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); - var holes = []; - var i, ii, holeFlatCoords; - for (i = 1, ii = linearRings.length; i < ii; ++i) { - holeFlatCoords = linearRings[i].getFlatCoordinates(); - holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, - stride, -this.origin_[0], -this.origin_[1]); - holes.push(holeFlatCoords); - this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); - } - this.drawCoordinates_(flatCoordinates, holes, stride); - } -}; - - -/** - * @param {ol.webgl.Context} context Context. - **/ -ol.render.webgl.PolygonReplay.prototype.finish = function(context) { - // create, bind, and populate the vertices buffer - this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); - - // create, bind, and populate the indices buffer - this.indicesBuffer_ = new ol.webgl.Buffer(this.indices_); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - - this.startIndices_.push(this.indices_.length); - - this.lineStringReplay_.finish(context); - - //Clean up, if there is nothing to draw - if (this.styleIndices_.length === 0 && this.styles_.length > 0) { - this.styles_ = []; - } - - this.vertices_ = null; - this.indices_ = null; -}; - - -/** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. - */ -ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(context) { - // We only delete our stuff here. The shaders and the program may - // be used by other PolygonReplay instances (for other layers). And - // they will be deleted when disposing of the ol.webgl.Context - // object. - goog.asserts.assert(!goog.isNull(this.verticesBuffer_), - 'verticesBuffer must not be null'); - goog.asserts.assert(!goog.isNull(this.indicesBuffer_), - 'indicesBuffer must not be null'); - var verticesBuffer = this.verticesBuffer_; - var indicesBuffer = this.indicesBuffer_; - var lineDeleter = this.lineStringReplay_.getDeleteResourcesFunction(context); - return function() { - context.deleteBuffer(verticesBuffer); - context.deleteBuffer(indicesBuffer); - lineDeleter(); - }; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @return {ol.render.webgl.polygonreplay.shader.Default.Locations} Locations. - */ -ol.render.webgl.PolygonReplay.prototype.setUpProgram_ = function(gl, context, size, pixelRatio) { - // get the program - var fragmentShader, vertexShader; - fragmentShader = - ol.render.webgl.polygonreplay.shader.DefaultFragment.getInstance(); - vertexShader = - ol.render.webgl.polygonreplay.shader.DefaultVertex.getInstance(); - var program = context.getProgram(fragmentShader, vertexShader); - - // get the locations - var locations; - if (!this.defaultLocations_) { - locations = new ol.render.webgl.polygonreplay.shader.Default - .Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - context.useProgram(program); - - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 8, 0); - - return locations; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {boolean} hitDetection Hit detection mode. - */ -ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { - //Save GL parameters. - var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); - var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); - - if (!hitDetection) { - gl.enable(gl.DEPTH_TEST); - gl.depthMask(true); - gl.depthFunc(gl.NOTEQUAL); - } - - if (!ol.object.isEmpty(skippedFeaturesHash)) { - this.drawReplaySkipping_(gl, context, skippedFeaturesHash); - } else { - goog.asserts.assert(this.styles_.length === this.styleIndices_.length, - 'number of styles and styleIndices match'); - - //Draw by style groups to minimize drawElements() calls. - var i, start, end, nextStyle; - end = this.startIndices_[this.startIndices_.length - 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - start = this.styleIndices_[i]; - nextStyle = this.styles_[i]; - this.setFillStyle_(gl, nextStyle); - this.drawElements_(gl, context, start, end); - end = start; - } - } - if (!hitDetection) { - gl.clear(gl.DEPTH_BUFFER_BIT); - gl.disable(gl.DEPTH_TEST); - //Restore GL parameters. - gl.depthMask(tmpDepthMask); - gl.depthFunc(tmpDepthFunc); - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - goog.asserts.assert(this.styles_.length === this.styleIndices_.length, - 'number of styles and styleIndices match'); - goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, - 'number of startIndices and startIndicesFeature match'); - - var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; - featureIndex = this.startIndices_.length - 2; - end = this.startIndices_[featureIndex + 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - nextStyle = this.styles_[i]; - this.setFillStyle_(gl, nextStyle); - groupStart = this.styleIndices_[i]; - - while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - start = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; - featureUid = goog.getUid(feature).toString(); - - if (skippedFeaturesHash[featureUid] === undefined && - feature.getGeometry() && - (opt_hitExtent === undefined || ol.extent.intersects( - /** @type {Array} */ (opt_hitExtent), - feature.getGeometry().getExtent()))) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements_(gl, context, start, end); - - var result = featureCallback(feature); - - if (result) { - return result; - } - - } - featureIndex--; - end = start; - } - } - return undefined; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object} skippedFeaturesHash Ids of features to skip. - */ -ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { - goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, - 'number of startIndices and startIndicesFeature match'); - - var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; - featureIndex = this.startIndices_.length - 2; - end = start = this.startIndices_[featureIndex + 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - nextStyle = this.styles_[i]; - this.setFillStyle_(gl, nextStyle); - groupStart = this.styleIndices_[i]; - - while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - featureStart = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; - featureUid = goog.getUid(feature).toString(); - - if (skippedFeaturesHash[featureUid]) { - if (start !== end) { - this.drawElements_(gl, context, start, end); - } - end = featureStart; - } - featureIndex--; - start = featureStart; - } - if (start !== end) { - this.drawElements_(gl, context, start, end); - } - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {Array.} color Color. - */ -ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) { - gl.uniform4fv(this.defaultLocations_.u_color, color); -}; - - -/** - * @inheritDoc - */ - -ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - goog.asserts.assert(this.state_, 'this.state_ should not be null'); - goog.asserts.assert(fillStyle || strokeStyle, 'one of the styles should not be null'); - var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0]; - if (!(fillStyleColor instanceof CanvasGradient) && - !(fillStyleColor instanceof CanvasPattern)) { - fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) { - return i != 3 ? c / 255 : c; - }) || ol.render.webgl.defaultFillStyle; - } else { - fillStyleColor = ol.render.webgl.defaultFillStyle; - } - if (!this.state_.fillColor || !ol.array.equals(fillStyleColor, this.state_.fillColor)) { - this.state_.fillColor = fillStyleColor; - this.state_.changed = true; - this.styles_.push(fillStyleColor); - } - //Provide a null stroke style, if no strokeStyle is provided. Required for the draw interaction to work. - if (strokeStyle) { - this.lineStringReplay_.setFillStrokeStyle(null, strokeStyle); - } else { - var nullStrokeStyle = new ol.style.Stroke({ - color: [0, 0, 0, 0], - lineWidth: 0 - }); - this.lineStringReplay_.setFillStrokeStyle(null, nullStrokeStyle); - } -}; - - -/** - * @constructor - * @extends {ol.render.ReplayGroup} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @param {number=} opt_renderBuffer Render buffer. - * @struct - */ -ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { - ol.render.ReplayGroup.call(this); - - /** - * @type {ol.Extent} - * @private - */ - this.maxExtent_ = maxExtent; - - /** - * @type {number} - * @private - */ - this.tolerance_ = tolerance; - - /** - * @type {number|undefined} - * @private - */ - this.renderBuffer_ = opt_renderBuffer; - - /** - * ImageReplay only is supported at this point. - * @type {Object.} - * @private - */ - this.replays_ = {}; - -}; -ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); - - -/** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. - */ -ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { - var functions = []; - var replayKey; - for (replayKey in this.replays_) { - functions.push( - this.replays_[replayKey].getDeleteResourcesFunction(context)); - } - return function() { - var length = functions.length; - var result; - for (var i = 0; i < length; i++) { - result = functions[i].apply(this, arguments); - } - return result; - }; -}; - - -/** - * @param {ol.webgl.Context} context Context. - */ -ol.render.webgl.ReplayGroup.prototype.finish = function(context) { - var replayKey; - for (replayKey in this.replays_) { - this.replays_[replayKey].finish(context); - } -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { - var replay = this.replays_[replayType]; - if (replay === undefined) { - var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType]; - replay = new constructor(this.tolerance_, this.maxExtent_); - this.replays_[replayType] = replay; - } - return replay; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { - return ol.obj.isEmpty(this.replays_); -}; - - -/** - * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @param {number} opacity Global opacity. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - */ -ol.render.webgl.ReplayGroup.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash) { - var i, ii, replay; - for (i = 0, ii = ol.render.replay.ORDER.length; i < ii; ++i) { - replay = this.replays_[ol.render.replay.ORDER[i]]; - if (replay !== undefined) { - replay.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - undefined, false); - } - } -}; - - -/** - * @private - * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @param {number} opacity Global opacity. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, - center, resolution, rotation, size, pixelRatio, opacity, - skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) { - var i, replay, result; - for (i = ol.render.replay.ORDER.length - 1; i >= 0; --i) { - replay = this.replays_[ol.render.replay.ORDER[i]]; - if (replay !== undefined) { - result = replay.replay(context, - center, resolution, rotation, size, pixelRatio, opacity, - skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); - if (result) { - return result; - } - } - } - return undefined; -}; - - -/** - * @param {ol.Coordinate} coordinate Coordinate. - * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @param {number} opacity Global opacity. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( - coordinate, context, center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - callback) { - var gl = context.getGL(); - gl.bindFramebuffer( - gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); - - - /** - * @type {ol.Extent} - */ - var hitExtent; - if (this.renderBuffer_ !== undefined) { - // build an extent around the coordinate, so that only features that - // intersect this extent are checked - hitExtent = ol.extent.buffer( - ol.extent.createOrUpdateFromCoordinate(coordinate), - resolution * this.renderBuffer_); - } - - return this.replayHitDetection_(context, - coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_, - pixelRatio, opacity, skippedFeaturesHash, - /** - * @param {ol.Feature|ol.render.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - var imageData = new Uint8Array(4); - gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); - - if (imageData[3] > 0) { - var result = callback(feature); - if (result) { - return result; - } - } - }, true, hitExtent); -}; - - -/** - * @param {ol.Coordinate} coordinate Coordinate. - * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @param {number} opacity Global opacity. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @return {boolean} Is there a feature at the given coordinate? - */ -ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( - coordinate, context, center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash) { - var gl = context.getGL(); - gl.bindFramebuffer( - gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); - - var hasFeature = this.replayHitDetection_(context, - coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_, - pixelRatio, opacity, skippedFeaturesHash, - /** - * @param {ol.Feature|ol.render.Feature} feature Feature. - * @return {boolean} Is there a feature? - */ - function(feature) { - var imageData = new Uint8Array(4); - gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); - return imageData[3] > 0; - }, false); - - return hasFeature !== undefined; -}; - - -/** - * @const - * @private - * @type {Object.} - */ -ol.render.webgl.BATCH_CONSTRUCTORS_ = { - 'Image': ol.render.webgl.ImageReplay, - 'LineString': ol.render.webgl.LineStringReplay, - 'Polygon': ol.render.webgl.PolygonReplay -}; - - -/** - * @const - * @private - * @type {Array.} - */ -ol.render.webgl.HIT_DETECTION_SIZE_ = [1, 1]; diff --git a/src/ol/render/webgl/webgl.js b/src/ol/render/webgl/index.js similarity index 100% rename from src/ol/render/webgl/webgl.js rename to src/ol/render/webgl/index.js diff --git a/src/ol/render/webgl/webgl.jsdoc b/src/ol/render/webgl/index.jsdoc similarity index 100% rename from src/ol/render/webgl/webgl.jsdoc rename to src/ol/render/webgl/index.jsdoc diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/linestringreplay/defaultshader.glsl similarity index 97% rename from src/ol/render/webgl/webgllinestringdefault.glsl rename to src/ol/render/webgl/linestringreplay/defaultshader.glsl index 72edd05366..e18f2f70eb 100644 --- a/src/ol/render/webgl/webgllinestringdefault.glsl +++ b/src/ol/render/webgl/linestringreplay/defaultshader.glsl @@ -1,5 +1,5 @@ -//! NAMESPACE=ol.render.webgl.linestringreplay.shader.Default -//! CLASS=ol.render.webgl.linestringreplay.shader.Default +//! NAMESPACE=ol.render.webgl.linestringreplay.defaultshader +//! CLASS=ol.render.webgl.linestringreplay.defaultshader //! COMMON diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/linestringreplay/defaultshader.js similarity index 80% rename from src/ol/render/webgl/webgllinestringdefaultshader.js rename to src/ol/render/webgl/linestringreplay/defaultshader.js index bec5b5baaf..cf48042084 100644 --- a/src/ol/render/webgl/webgllinestringdefaultshader.js +++ b/src/ol/render/webgl/linestringreplay/defaultshader.js @@ -1,80 +1,83 @@ // This file is automatically generated, do not edit -goog.provide('ol.render.webgl.linestringreplay.shader.Default'); -goog.provide('ol.render.webgl.linestringreplay.shader.Default.Locations'); -goog.provide('ol.render.webgl.linestringreplay.shader.DefaultFragment'); -goog.provide('ol.render.webgl.linestringreplay.shader.DefaultVertex'); +goog.provide('ol.render.webgl.linestringreplay.defaultshader'); -goog.require('ol.webgl.shader'); +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); /** * @constructor - * @extends {ol.webgl.shader.Fragment} + * @extends {ol.webgl.Fragment} * @struct */ -ol.render.webgl.linestringreplay.shader.DefaultFragment = function() { - ol.webgl.shader.Fragment.call(this, ol.render.webgl.linestringreplay.shader.DefaultFragment.SOURCE); +ol.render.webgl.linestringreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE); }; -ol.inherits(ol.render.webgl.linestringreplay.shader.DefaultFragment, ol.webgl.shader.Fragment); -goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultFragment); +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Fragment, ol.webgl.Fragment); /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; +ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float l;uniform vec4 m;uniform vec2 n;uniform float o;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*n.x*o,(b.y+1.0)/2.0*n.y*o);if(length(windowCoords-gl_FragCoord.xy)>c*o){discard;}} gl_FragColor=m;float alpha=m.a*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; +ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float l;uniform vec4 m;uniform vec2 n;uniform float o;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*n.x*o,(b.y+1.0)/2.0*n.y*o);if(length(windowCoords-gl_FragCoord.xy)>c*o){discard;}} gl_FragColor=m;float alpha=m.a*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? - ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE : - ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE; +ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.fragment = new ol.render.webgl.linestringreplay.defaultshader.Fragment(); /** * @constructor - * @extends {ol.webgl.shader.Vertex} + * @extends {ol.webgl.Vertex} * @struct */ -ol.render.webgl.linestringreplay.shader.DefaultVertex = function() { - ol.webgl.shader.Vertex.call(this, ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE); +ol.render.webgl.linestringreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE); }; -ol.inherits(ol.render.webgl.linestringreplay.shader.DefaultVertex, ol.webgl.shader.Vertex); -goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vertex); /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n float miterLimit = u_miterLimit + u_lineWidth;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n float miterLimit = u_miterLimit + u_lineWidth;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){bool degenerate=false;c=j/2.0;float miterLimit=k+j;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+i*vec4(longOffset,0.,0.);vec4 p2=projPos+i*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+i*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+i*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=i*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){bool degenerate=false;c=j/2.0;float miterLimit=k+j;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+i*vec4(longOffset,0.,0.);vec4 p2=projPos+i*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+i*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+i*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=i*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? - ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE : - ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE; +ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.vertex = new ol.render.webgl.linestringreplay.defaultshader.Vertex(); /** @@ -83,77 +86,77 @@ ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? * @param {WebGLProgram} program Program. * @struct */ -ol.render.webgl.linestringreplay.shader.Default.Locations = function(gl, program) { +ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) { /** * @type {WebGLUniformLocation} */ this.u_color = gl.getUniformLocation( - program, goog.DEBUG ? 'u_color' : 'm'); + program, ol.DEBUG ? 'u_color' : 'm'); /** * @type {WebGLUniformLocation} */ this.u_lineWidth = gl.getUniformLocation( - program, goog.DEBUG ? 'u_lineWidth' : 'j'); + program, ol.DEBUG ? 'u_lineWidth' : 'j'); /** * @type {WebGLUniformLocation} */ this.u_miterLimit = gl.getUniformLocation( - program, goog.DEBUG ? 'u_miterLimit' : 'k'); + program, ol.DEBUG ? 'u_miterLimit' : 'k'); /** * @type {WebGLUniformLocation} */ this.u_offsetScaleMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i'); /** * @type {WebGLUniformLocation} */ this.u_opacity = gl.getUniformLocation( - program, goog.DEBUG ? 'u_opacity' : 'l'); + program, ol.DEBUG ? 'u_opacity' : 'l'); /** * @type {WebGLUniformLocation} */ this.u_pixelRatio = gl.getUniformLocation( - program, goog.DEBUG ? 'u_pixelRatio' : 'o'); + program, ol.DEBUG ? 'u_pixelRatio' : 'o'); /** * @type {WebGLUniformLocation} */ this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'h'); + program, ol.DEBUG ? 'u_projectionMatrix' : 'h'); /** * @type {WebGLUniformLocation} */ this.u_size = gl.getUniformLocation( - program, goog.DEBUG ? 'u_size' : 'n'); + program, ol.DEBUG ? 'u_size' : 'n'); /** * @type {number} */ this.a_direction = gl.getAttribLocation( - program, goog.DEBUG ? 'a_direction' : 'g'); + program, ol.DEBUG ? 'a_direction' : 'g'); /** * @type {number} */ this.a_lastPos = gl.getAttribLocation( - program, goog.DEBUG ? 'a_lastPos' : 'd'); + program, ol.DEBUG ? 'a_lastPos' : 'd'); /** * @type {number} */ this.a_nextPos = gl.getAttribLocation( - program, goog.DEBUG ? 'a_nextPos' : 'f'); + program, ol.DEBUG ? 'a_nextPos' : 'f'); /** * @type {number} */ this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'e'); + program, ol.DEBUG ? 'a_position' : 'e'); }; diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js new file mode 100644 index 0000000000..498121129b --- /dev/null +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -0,0 +1,654 @@ +goog.provide('ol.render.webgl.LineStringReplay'); + +goog.require('ol'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.geom.flat.topology'); +goog.require('ol.obj'); +goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl.linestringreplay.defaultshader'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.linestringreplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {{strokeColor: (Array.|null), + * lineCap: (string|undefined), + * lineDash: Array., + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + strokeColor: null, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); + + +/** + * Draw one segment. + * @private + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ +ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + + var i, ii; + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + //To save a vertex, the direction of a point is a product of the sign (1 or -1), a prime from + //ol.render.webgl.lineStringInstruction, and a rounding factor (1 or 2). If the product is even, + //we round it. If it is odd, we don't. + var lineJoin = this.state_.lineJoin === 'bevel' ? 0 : + this.state_.lineJoin === 'miter' ? 1 : 2; + var lineCap = this.state_.lineCap === 'butt' ? 0 : + this.state_.lineCap === 'square' ? 1 : 2; + var closed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, offset, end, stride); + var startCoords, sign, n; + var lastIndex = numIndices; + var lastSign = 1; + //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. + var p0, p1, p2; + + for (i = offset, ii = end; i < ii; i += stride) { + + n = numVertices / 7; + + p0 = p1; + p1 = p2 || [flatCoordinates[i], flatCoordinates[i + 1]]; + //First vertex. + if (i === offset) { + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { + break; + } + if (closed) { + //A closed line! Complete the circle. + p0 = [flatCoordinates[end - stride * 2], + flatCoordinates[end - stride * 2 + 1]]; + + startCoords = p2; + } else { + //Add the first two/four vertices. + + if (lineCap) { + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n + 2; + + } + + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + lastIndex = numVertices / 7 - 1; + + continue; + } + } else if (i === end - stride) { + //Last vertex. + if (closed) { + //Same as the first vertex. + p2 = startCoords; + break; + } else { + //For the compiler not to complain. This will never be [0, 0]. + p0 = p0 || [0, 0]; + + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); + + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = lastIndex; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n; + + if (lineCap) { + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n + 2; + + } + + break; + } + } else { + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + } + + sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) + ? -1 : 1; + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + if (i > offset) { + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; + } + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 1; + + lastIndex = n + 2; + lastSign = sign; + + //Add miter + if (lineJoin) { + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.MITER_TOP * lineJoin, numVertices); + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n; + } + } + + if (closed) { + //Link the last triangle/rhombus to the first one. + //n will never be numVertices / 7 here. However, the compiler complains otherwise. + n = n || numVertices / 7; + sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) + ? 1 : -1; + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; + } +}; + +/** + * @param {Array.} p0 Last coordinates. + * @param {Array.} p1 Current coordinates. + * @param {Array.} p2 Next coordinates. + * @param {number} product Sign, instruction, and rounding product. + * @param {number} numVertices Vertex counter. + * @return {number} Vertex counter. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.addVertices_ = function(p0, p1, p2, product, numVertices) { + this.vertices[numVertices++] = p0[0]; + this.vertices[numVertices++] = p0[1]; + this.vertices[numVertices++] = p1[0]; + this.vertices[numVertices++] = p1[1]; + this.vertices[numVertices++] = p2[0]; + this.vertices[numVertices++] = p2[1]; + this.vertices[numVertices++] = product; + + return numVertices; +}; + +/** + * Check if the linestring can be drawn (i. e. valid). + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} The linestring can be drawn. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, offset, end, stride) { + var range = end - offset; + if (range < stride * 2) { + return false; + } else if (range === stride * 2) { + var firstP = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var lastP = [flatCoordinates[offset + stride], flatCoordinates[offset + stride + 1]]; + return !ol.array.equals(firstP, lastP); + } + + return true; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { + var flatCoordinates = lineStringGeometry.getFlatCoordinates(); + var stride = lineStringGeometry.getStride(); + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { + var indexCount = this.indices.length; + var lineStringGeometries = multiLineStringGeometry.getLineStrings(); + var i, ii; + for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { + var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); + var stride = lineStringGeometries[i].getStride(); + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } + } + if (this.indices.length > indexCount) { + this.startIndices.push(indexCount); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(indexCount); + this.state_.changed = false; + } + } +}; + + +/** + * @param {Array.} flatCoordinates Flat coordinates. + * @param {Array.>} holeFlatCoordinates Hole flat coordinates. + * @param {number} stride Stride. + */ +ol.render.webgl.LineStringReplay.prototype.drawPolygonCoordinates = function( + flatCoordinates, holeFlatCoordinates, stride) { + this.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); + if (holeFlatCoordinates.length) { + var i, ii; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + this.drawCoordinates_(holeFlatCoordinates[i], 0, + holeFlatCoordinates[i].length, stride); + } + } +}; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {number=} opt_index Index count. + */ +ol.render.webgl.LineStringReplay.prototype.setPolygonStyle = function(feature, opt_index) { + var index = opt_index === undefined ? this.indices.length : opt_index; + this.startIndices.push(index); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(index); + this.state_.changed = false; + } +}; + + +/** + * @return {number} Current index. + */ +ol.render.webgl.LineStringReplay.prototype.getCurrentIndex = function() { + return this.indices.length; +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.LineStringReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other LineStringReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, 'verticesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.linestringreplay.defaultshader.fragment; + vertexShader = ol.render.webgl.linestringreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.linestringreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_lastPos); + gl.vertexAttribPointer(locations.a_lastPos, 2, ol.webgl.FLOAT, + false, 28, 0); + + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 28, 8); + + gl.enableVertexAttribArray(locations.a_nextPos); + gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT, + false, 28, 16); + + gl.enableVertexAttribArray(locations.a_direction); + gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT, + false, 28, 24); + + // Enable renderer specific uniforms. + gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_pixelRatio, pixelRatio); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + this.drawElements(gl, context, start, end); + end = start; + } + } + if (!hitDetection) { + gl.clear(gl.DEPTH_BUFFER_BIT); + gl.disable(gl.DEPTH_TEST); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.} color Color. + * @param {number} lineWidth Line width. + * @param {number} miterLimit Miter limit. + */ +ol.render.webgl.LineStringReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth, miterLimit) { + gl.uniform4fv(this.defaultLocations_.u_color, color); + gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); + gl.uniform1f(this.defaultLocations_.u_miterLimit, miterLimit); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var strokeStyleLineCap = strokeStyle.getLineCap(); + this.state_.lineCap = strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.webgl.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.webgl.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + this.state_.lineJoin = strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.webgl.defaultLineJoin; + var strokeStyleColor = strokeStyle.getColor(); + if (!(strokeStyleColor instanceof CanvasGradient) && + !(strokeStyleColor instanceof CanvasPattern)) { + strokeStyleColor = ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultStrokeStyle; + } else { + strokeStyleColor = ol.render.webgl.defaultStrokeStyle; + } + var strokeStyleWidth = strokeStyle.getWidth(); + strokeStyleWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.webgl.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + strokeStyleMiterLimit = strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.webgl.defaultMiterLimit; + if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || + this.state_.lineWidth !== strokeStyleWidth || this.state_.miterLimit !== strokeStyleMiterLimit) { + this.state_.changed = true; + this.state_.strokeColor = strokeStyleColor; + this.state_.lineWidth = strokeStyleWidth; + this.state_.miterLimit = strokeStyleMiterLimit; + this.styles_.push([strokeStyleColor, strokeStyleWidth, strokeStyleMiterLimit]); + } +}; diff --git a/src/ol/render/webgl/webglpolygondefault.glsl b/src/ol/render/webgl/polygonreplay/defaultshader.glsl similarity index 82% rename from src/ol/render/webgl/webglpolygondefault.glsl rename to src/ol/render/webgl/polygonreplay/defaultshader.glsl index fe31b75ad4..9f5102bcda 100644 --- a/src/ol/render/webgl/webglpolygondefault.glsl +++ b/src/ol/render/webgl/polygonreplay/defaultshader.glsl @@ -1,5 +1,5 @@ -//! NAMESPACE=ol.render.webgl.polygonreplay.shader.Default -//! CLASS=ol.render.webgl.polygonreplay.shader.Default +//! NAMESPACE=ol.render.webgl.polygonreplay.defaultshader +//! CLASS=ol.render.webgl.polygonreplay.defaultshader //! COMMON diff --git a/src/ol/render/webgl/polygonreplay/defaultshader.js b/src/ol/render/webgl/polygonreplay/defaultshader.js new file mode 100644 index 0000000000..7c691999e9 --- /dev/null +++ b/src/ol/render/webgl/polygonreplay/defaultshader.js @@ -0,0 +1,126 @@ +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.polygonreplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.fragment = new ol.render.webgl.polygonreplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n vec4 offsets = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){vec4 offsets=c*vec4(0.,0.,0.,0.);gl_Position=b*vec4(a,0.,1.)+offsets;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.vertex = new ol.render.webgl.polygonreplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, ol.DEBUG ? 'u_color' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'd'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'c'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'f'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'b'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'a'); +}; diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js new file mode 100644 index 0000000000..427fd3e597 --- /dev/null +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -0,0 +1,1018 @@ +goog.provide('ol.render.webgl.PolygonReplay'); + +goog.require('ol'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.render.webgl.polygonreplay.defaultshader'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl'); +goog.require('ol.style.Stroke'); +goog.require('ol.structs.LinkedList'); +goog.require('ol.structs.RBush'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + this.lineStringReplay = new ol.render.webgl.LineStringReplay( + tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.polygonreplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {{fillColor: (Array.|null), + * changed: boolean}|null} + */ + this.state_ = { + fillColor: null, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); + + +/** + * Draw one polygon. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {Array.>} holeFlatCoordinates Hole flat coordinates. + * @param {number} stride Stride. + * @private + */ +ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( + flatCoordinates, holeFlatCoordinates, stride) { + // Triangulate the polygon + var outerRing = new ol.structs.LinkedList(); + var rtree = new ol.structs.RBush(); + // Initialize the outer ring + var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true); + + // Eliminate holes, if there are any + if (holeFlatCoordinates.length) { + var i, ii; + var holeLists = []; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + var holeList = { + list: new ol.structs.LinkedList(), + maxX: undefined + }; + holeLists.push(holeList); + holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i], + stride, holeList.list, rtree, false); + } + holeLists.sort(function(a, b) { + return b.maxX - a.maxX; + }); + for (i = 0; i < holeLists.length; ++i) { + this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX, rtree); + } + } + this.classifyPoints_(outerRing, rtree, false); + this.triangulate_(outerRing, rtree); +}; + + +/** + * Inserts flat coordinates in a linked list and adds them to the vertex buffer. + * @private + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} stride Stride. + * @param {ol.structs.LinkedList} list Linked list. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} clockwise Coordinate order should be clockwise. + * @return {number} Maximum X value. + */ +ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( + flatCoordinates, stride, list, rtree, clockwise) { + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, + 0, flatCoordinates.length, stride); + var i, ii, maxX; + var n = this.vertices.length / 2; + /** @type {ol.WebglPolygonVertex} */ + var start; + /** @type {ol.WebglPolygonVertex} */ + var p0; + /** @type {ol.WebglPolygonVertex} */ + var p1; + var extents = []; + var segments = []; + if (clockwise === isClockwise) { + start = this.createPoint_(flatCoordinates[0], flatCoordinates[1], n++); + p0 = start; + maxX = flatCoordinates[0]; + for (i = stride, ii = flatCoordinates.length; i < ii; i += stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + segments.push(this.insertItem_(p1, start, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + } else { + var end = flatCoordinates.length - stride; + start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++); + p0 = start; + maxX = flatCoordinates[end]; + for (i = end - stride, ii = 0; i >= ii; i -= stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + segments.push(this.insertItem_(p1, start, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + } + rtree.load(extents, segments); + + return maxX; +}; + + +/** + * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices. + * @private + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} ccw The orientation of the polygon is counter-clockwise. + * @return {boolean} There were reclassified points. + */ +ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) { + var start = list.firstItem(); + var s0 = start; + var s1 = list.nextItem(); + var pointsReclassified = false; + do { + var reflex = ccw ? ol.render.webgl.triangleIsCounterClockwise(s1.p1.x, + s1.p1.y, s0.p1.x, s0.p1.y, s0.p0.x, s0.p0.y) : + ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x, + s0.p1.y, s1.p1.x, s1.p1.y); + if (reflex === undefined) { + this.removeItem_(s0, s1, list, rtree); + pointsReclassified = true; + if (s1 === start) { + start = list.getNextItem(); + } + s1 = s0; + list.prevItem(); + } else if (s0.p1.reflex !== reflex) { + s0.p1.reflex = reflex; + pointsReclassified = true; + } + s0 = s1; + s1 = list.nextItem(); + } while (s0 !== start); + return pointsReclassified; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} hole Linked list of the hole. + * @param {number} holeMaxX Maximum X value of the hole. + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {number} listMaxX Maximum X value of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, + list, listMaxX, rtree) { + var seg = hole.firstItem(); + while (seg.p1.x !== holeMaxX) { + seg = hole.nextItem(); + } + + var p1 = seg.p1; + /** @type {ol.WebglPolygonVertex} */ + var p2 = {x: listMaxX, y: p1.y, i: -1}; + var minDist = Infinity; + var i, ii, bestPoint; + /** @type {ol.WebglPolygonVertex} */ + var p5; + + var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); + for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { + var currSeg = intersectingSegments[i]; + if (currSeg.p0 !== p1 && currSeg.p1 !== p1) { + var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, + currSeg.p1, true); + var dist = Math.abs(p1.x - intersection[0]); + if (dist < minDist) { + minDist = dist; + p5 = {x: intersection[0], y: intersection[1], i: -1}; + seg = currSeg; + } + } + } + bestPoint = seg.p1; + + var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree); + if (pointsInTriangle.length) { + var theta = Infinity; + for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) { + var currPoint = pointsInTriangle[i]; + var currTheta = Math.atan2(p1.y - currPoint.y, p2.x - currPoint.x); + if (currTheta < theta || (currTheta === theta && currPoint.x < bestPoint.x)) { + theta = currTheta; + bestPoint = currPoint; + } + } + } + + seg = list.firstItem(); + while (seg.p1 !== bestPoint) { + seg = list.nextItem(); + } + + //We clone the bridge points as they can have different convexity. + var p0Bridge = {x: p1.x, y: p1.y, i: p1.i, reflex: undefined}; + var p1Bridge = {x: seg.p1.x, y: seg.p1.y, i: seg.p1.i, reflex: undefined}; + + hole.getNextItem().p0 = p0Bridge; + this.insertItem_(p1, seg.p1, hole, rtree); + this.insertItem_(p1Bridge, p0Bridge, hole, rtree); + seg.p1 = p1Bridge; + hole.setFirstItem(); + list.concat(hole); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { + var ccw = false; + var simple = this.isSimple_(list, rtree); + + // Start clipping ears + while (list.getLength() > 3) { + if (simple) { + if (!this.clipEars_(list, rtree, simple, ccw)) { + if (!this.classifyPoints_(list, rtree, ccw)) { + // Due to the behavior of OL's PIP algorithm, the ear clipping cannot + // introduce touching segments. However, the original data may have some. + if (!this.resolveLocalSelfIntersections_(list, rtree, true)) { + // Something went wrong. + break; + } + } + } + } else { + if (!this.clipEars_(list, rtree, simple, ccw)) { + // We ran out of ears, try to reclassify. + if (!this.classifyPoints_(list, rtree, ccw)) { + // We have a bad polygon, try to resolve local self-intersections. + if (!this.resolveLocalSelfIntersections_(list, rtree)) { + simple = this.isSimple_(list, rtree); + if (!simple) { + // We have a really bad polygon, try more time consuming methods. + this.splitPolygon_(list, rtree); + break; + } else { + ccw = !this.isClockwise_(list); + this.classifyPoints_(list, rtree, ccw); + } + } + } + } + } + } + if (list.getLength() === 3) { + var numIndices = this.indices.length; + this.indices[numIndices++] = list.getPrevItem().p0.i; + this.indices[numIndices++] = list.getCurrItem().p0.i; + this.indices[numIndices++] = list.getNextItem().p0.i; + } +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} simple The polygon is simple. + * @param {boolean} ccw Orientation of the polygon is counter-clockwise. + * @return {boolean} There were processed ears. + */ +ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple, ccw) { + var numIndices = this.indices.length; + var start = list.firstItem(); + var s0 = list.getPrevItem(); + var s1 = start; + var s2 = list.nextItem(); + var s3 = list.getNextItem(); + var p0, p1, p2; + var processedEars = false; + do { + p0 = s1.p0; + p1 = s1.p1; + p2 = s2.p1; + if (p1.reflex === false) { + // We might have a valid ear + var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0, + s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1); + if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) && + diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) { + //The diagonal is completely inside the polygon + if (simple || p0.reflex === false || p2.reflex === false || + ol.geom.flat.orient.linearRingIsClockwise([s0.p0.x, s0.p0.y, p0.x, + p0.y, p1.x, p1.y, p2.x, p2.y, s3.p1.x, s3.p1.y], 0, 10, 2) === !ccw) { + //The diagonal is persumably valid, we have an ear + this.indices[numIndices++] = p0.i; + this.indices[numIndices++] = p1.i; + this.indices[numIndices++] = p2.i; + this.removeItem_(s1, s2, list, rtree); + if (s2 === start) { + start = s3; + } + processedEars = true; + } + } + } + // Else we have a reflex point. + s0 = list.getPrevItem(); + s1 = list.getCurrItem(); + s2 = list.nextItem(); + s3 = list.getNextItem(); + } while (s1 !== start && list.getLength() > 3); + + return processedEars; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Resolve touching segments. + * @return {boolean} There were resolved intersections. +*/ +ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function( + list, rtree, opt_touch) { + var start = list.firstItem(); + list.nextItem(); + var s0 = start; + var s1 = list.nextItem(); + var resolvedIntersections = false; + + do { + var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1, + opt_touch); + if (intersection) { + var breakCond = false; + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + var n = numVertices / 2; + var seg = list.prevItem(); + list.removeItem(); + rtree.remove(seg); + breakCond = (seg === start); + var p; + if (opt_touch) { + if (intersection[0] === s0.p0.x && intersection[1] === s0.p0.y) { + list.prevItem(); + p = s0.p0; + s1.p0 = p; + rtree.remove(s0); + breakCond = breakCond || (s0 === start); + } else { + p = s1.p1; + s0.p1 = p; + rtree.remove(s1); + breakCond = breakCond || (s1 === start); + } + list.removeItem(); + } else { + p = this.createPoint_(intersection[0], intersection[1], n); + s0.p1 = p; + s1.p0 = p; + rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); + rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y), + Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1); + } + + this.indices[numIndices++] = seg.p0.i; + this.indices[numIndices++] = seg.p1.i; + this.indices[numIndices++] = p.i; + + resolvedIntersections = true; + if (breakCond) { + break; + } + } + + s0 = list.getPrevItem(); + s1 = list.nextItem(); + } while (s0 !== start); + return resolvedIntersections; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @return {boolean} The polygon is simple. + */ +ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) { + var start = list.firstItem(); + var seg = start; + do { + if (this.getIntersections_(seg, rtree).length) { + return false; + } + seg = list.nextItem(); + } while (seg !== start); + return true; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @return {boolean} Orientation is clockwise. + */ +ol.render.webgl.PolygonReplay.prototype.isClockwise_ = function(list) { + var length = list.getLength() * 2; + var flatCoordinates = new Array(length); + var start = list.firstItem(); + var seg = start; + var i = 0; + do { + flatCoordinates[i++] = seg.p0.x; + flatCoordinates[i++] = seg.p0.y; + seg = list.nextItem(); + } while (seg !== start); + return ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, 0, length, 2); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) { + var start = list.firstItem(); + var s0 = start; + do { + var intersections = this.getIntersections_(s0, rtree); + if (intersections.length) { + var s1 = intersections[0]; + var n = this.vertices.length / 2; + var intersection = this.calculateIntersection_(s0.p0, + s0.p1, s1.p0, s1.p1); + var p = this.createPoint_(intersection[0], intersection[1], n); + var newPolygon = new ol.structs.LinkedList(); + var newRtree = new ol.structs.RBush(); + this.insertItem_(p, s0.p1, newPolygon, newRtree); + s0.p1 = p; + rtree.update([Math.min(s0.p0.x, p.x), Math.min(s0.p0.y, p.y), + Math.max(s0.p0.x, p.x), Math.max(s0.p0.y, p.y)], s0); + var currItem = list.nextItem(); + while (currItem !== s1) { + this.insertItem_(currItem.p0, currItem.p1, newPolygon, newRtree); + rtree.remove(currItem); + list.removeItem(); + currItem = list.getCurrItem(); + } + this.insertItem_(s1.p0, p, newPolygon, newRtree); + s1.p0 = p; + rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y), + Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1); + this.classifyPoints_(list, rtree, false); + this.triangulate_(list, rtree); + this.classifyPoints_(newPolygon, newRtree, false); + this.triangulate_(newPolygon, newRtree); + break; + } + s0 = list.nextItem(); + } while (s0 !== start); +}; + + +/** + * @private + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + * @param {number} i Index. + * @return {ol.WebglPolygonVertex} List item. + */ +ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { + var numVertices = this.vertices.length; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + /** @type {ol.WebglPolygonVertex} */ + var p = { + x: x, + y: y, + i: i, + reflex: undefined + }; + return p; +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 First point of segment. + * @param {ol.WebglPolygonVertex} p1 Second point of segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree. + * @return {ol.WebglPolygonSegment} segment. + */ +ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) { + var seg = { + p0: p0, + p1: p1 + }; + list.insertItem(seg); + if (opt_rtree) { + opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), + Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg); + } + return seg; +}; + + + /** + * @private + * @param {ol.WebglPolygonSegment} s0 Segment before the remove candidate. + * @param {ol.WebglPolygonSegment} s1 Remove candidate segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) { + list.removeItem(); + s0.p1 = s1.p1; + rtree.remove(s1); + rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 First point. + * @param {ol.WebglPolygonVertex} p1 Second point. + * @param {ol.WebglPolygonVertex} p2 Third point. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_reflex Only include reflex points. + * @return {Array.} Points in the triangle. + */ +ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, + p2, rtree, opt_reflex) { + var i, ii, j, p; + var result = []; + var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x, p2.x), + Math.min(p0.y, p1.y, p2.y), Math.max(p0.x, p1.x, p2.x), Math.max(p0.y, + p1.y, p2.y)]); + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + for (j in segmentsInExtent[i]) { + p = segmentsInExtent[i][j]; + if (p.x && p.y && (!opt_reflex || p.reflex)) { + if ((p.x !== p0.x || p.y !== p0.y) && (p.x !== p1.x || p.y !== p1.y) && + (p.x !== p2.x || p.y !== p2.y) && result.indexOf(p) === -1 && + ol.geom.flat.contains.linearRingContainsXY([p0.x, p0.y, p1.x, p1.y, + p2.x, p2.y], 0, 6, 2, p.x, p.y)) { + result.push(p); + } + } + } + } + return result; +}; + + +/** + * @private + * @param {ol.WebglPolygonSegment} segment Segment. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.} Intersecting segments. + */ +ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) { + var p0 = segment.p0; + var p1 = segment.p1; + var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x), + Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]); + var result = []; + var i, ii; + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + var currSeg = segmentsInExtent[i]; + if (segment !== currSeg && (opt_touch || currSeg.p0 !== p1 || currSeg.p1 !== p0) && + this.calculateIntersection_(p0, p1, currSeg.p0, currSeg.p1, opt_touch)) { + result.push(currSeg); + } + } + return result; +}; + + +/** + * Line intersection algorithm by Paul Bourke. + * @see http://paulbourke.net/geometry/pointlineplane/ + * + * @private + * @param {ol.WebglPolygonVertex} p0 First point. + * @param {ol.WebglPolygonVertex} p1 Second point. + * @param {ol.WebglPolygonVertex} p2 Third point. + * @param {ol.WebglPolygonVertex} p3 Fourth point. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.|undefined} Intersection coordinates. + */ +ol.render.webgl.PolygonReplay.prototype.calculateIntersection_ = function(p0, + p1, p2, p3, opt_touch) { + var denom = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y); + if (denom !== 0) { + var ua = ((p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)) / denom; + var ub = ((p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)) / denom; + if ((!opt_touch && ua > ol.render.webgl.EPSILON && ua < 1 - ol.render.webgl.EPSILON && + ub > ol.render.webgl.EPSILON && ub < 1 - ol.render.webgl.EPSILON) || (opt_touch && + ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)) { + return [p0.x + ua * (p1.x - p0.x), p0.y + ua * (p1.y - p0.y)]; + } + } + return undefined; +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 Point before the start of the diagonal. + * @param {ol.WebglPolygonVertex} p1 Start point of the diagonal. + * @param {ol.WebglPolygonVertex} p2 Ear candidate. + * @param {ol.WebglPolygonVertex} p3 End point of the diagonal. + * @param {ol.WebglPolygonVertex} p4 Point after the end of the diagonal. + * @return {boolean} Diagonal is inside the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.diagonalIsInside_ = function(p0, p1, p2, p3, p4) { + if (p1.reflex === undefined || p3.reflex === undefined) { + return false; + } + var p1IsLeftOf = (p2.x - p3.x) * (p1.y - p3.y) > (p2.y - p3.y) * (p1.x - p3.x); + var p1IsRightOf = (p4.x - p3.x) * (p1.y - p3.y) < (p4.y - p3.y) * (p1.x - p3.x); + var p3IsLeftOf = (p0.x - p1.x) * (p3.y - p1.y) > (p0.y - p1.y) * (p3.x - p1.x); + var p3IsRightOf = (p2.x - p1.x) * (p3.y - p1.y) < (p2.y - p1.y) * (p3.x - p1.x); + var p1InCone = p3.reflex ? p1IsRightOf || p1IsLeftOf : p1IsRightOf && p1IsLeftOf; + var p3InCone = p1.reflex ? p3IsRightOf || p3IsLeftOf : p3IsRightOf && p3IsLeftOf; + return p1InCone && p3InCone; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { + var polygons = multiPolygonGeometry.getPolygons(); + var stride = multiPolygonGeometry.getStride(); + var currIndex = this.indices.length; + var currLineIndex = this.lineStringReplay.getCurrentIndex(); + var i, ii, j, jj; + for (i = 0, ii = polygons.length; i < ii; ++i) { + var linearRings = polygons[i].getLinearRings(); + if (linearRings.length > 0) { + var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + var holes = []; + var holeFlatCoords; + for (j = 1, jj = linearRings.length; j < jj; ++j) { + holeFlatCoords = linearRings[j].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin[0], -this.origin[1]); + holes.push(holeFlatCoords); + } + this.lineStringReplay.drawPolygonCoordinates(flatCoordinates, holes, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); + } + } + if (this.indices.length > currIndex) { + this.startIndices.push(currIndex); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(currIndex); + this.state_.changed = false; + } + } + if (this.lineStringReplay.getCurrentIndex() > currLineIndex) { + this.lineStringReplay.setPolygonStyle(feature, currLineIndex); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { + var linearRings = polygonGeometry.getLinearRings(); + var stride = polygonGeometry.getStride(); + if (linearRings.length > 0) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + this.lineStringReplay.setPolygonStyle(feature); + + var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + var holes = []; + var i, ii, holeFlatCoords; + for (i = 1, ii = linearRings.length; i < ii; ++i) { + holeFlatCoords = linearRings[i].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin[0], -this.origin[1]); + holes.push(holeFlatCoords); + } + this.lineStringReplay.drawPolygonCoordinates(flatCoordinates, holes, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); + } +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.PolygonReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + this.lineStringReplay.finish(context); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other PolygonReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var lineDeleter = this.lineStringReplay.getDeleteResourcesFunction(context); + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + lineDeleter(); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.polygonreplay.defaultshader.fragment; + vertexShader = ol.render.webgl.polygonreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.polygonreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 8, 0); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + this.drawElements(gl, context, start, end); + end = start; + } + } + if (!hitDetection) { + gl.clear(gl.DEPTH_BUFFER_BIT); + gl.disable(gl.DEPTH_TEST); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + } + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.} color Color. + */ +ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) { + gl.uniform4fv(this.defaultLocations_.u_color, color); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0]; + if (!(fillStyleColor instanceof CanvasGradient) && + !(fillStyleColor instanceof CanvasPattern)) { + fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultFillStyle; + } else { + fillStyleColor = ol.render.webgl.defaultFillStyle; + } + if (!this.state_.fillColor || !ol.array.equals(fillStyleColor, this.state_.fillColor)) { + this.state_.fillColor = fillStyleColor; + this.state_.changed = true; + this.styles_.push(fillStyleColor); + } + //Provide a null stroke style, if no strokeStyle is provided. Required for the draw interaction to work. + if (strokeStyle) { + this.lineStringReplay.setFillStrokeStyle(null, strokeStyle); + } else { + var nullStrokeStyle = new ol.style.Stroke({ + color: [0, 0, 0, 0], + lineWidth: 0 + }); + this.lineStringReplay.setFillStrokeStyle(null, nullStrokeStyle); + } +}; diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js new file mode 100644 index 0000000000..7caed3e343 --- /dev/null +++ b/src/ol/render/webgl/replay.js @@ -0,0 +1,321 @@ +goog.provide('ol.render.webgl.Replay'); + +goog.require('ol'); +goog.require('ol.render.VectorContext'); +goog.require('ol.transform'); + +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.Replay = function(tolerance, maxExtent) { + ol.render.VectorContext.call(this); + + /** + * @protected + * @type {number} + */ + this.tolerance = tolerance; + + /** + * @protected + * @const + * @type {ol.Extent} + */ + this.maxExtent = maxExtent; + + /** + * The origin of the coordinate system for the point coordinates sent to + * the GPU. To eliminate jitter caused by precision problems in the GPU + * we use the "Rendering Relative to Eye" technique described in the "3D + * Engine Design for Virtual Globes" book. + * @protected + * @type {ol.Coordinate} + */ + this.origin = ol.extent.getCenter(maxExtent); + + /** + * @private + * @type {ol.Transform} + */ + this.projectionMatrix_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.offsetRotateMatrix_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.offsetScaleMatrix_ = ol.transform.create(); + + /** + * @private + * @type {Array.} + */ + this.tmpMat4_ = ol.vec.Mat4.create(); + + /** + * @protected + * @type {Array.} + */ + this.indices = []; + + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.indicesBuffer = null; + + /** + * Start index per feature (the index). + * @protected + * @type {Array.} + */ + this.startIndices = []; + + /** + * Start index per feature (the feature). + * @protected + * @type {Array.} + */ + this.startIndicesFeature = []; + + /** + * @protected + * @type {Array.} + */ + this.vertices = []; + + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.verticesBuffer = null; + + /** + * Optional parameter for PolygonReplay instances. + * @protected + * @type {ol.render.webgl.LineStringReplay|undefined} + */ + this.lineStringReplay = undefined; + +}; +ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext); + + +/** + * @abstract + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = function(context) {}; + + +/** + * @abstract + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.Replay.prototype.finish = function(context) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @return {ol.render.webgl.imagereplay.defaultshader.Locations| + ol.render.webgl.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations} Locations. + */ +ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {boolean} hitDetection Hit detection mode. + */ +ol.render.webgl.Replay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, featureCallback, opt_hitExtent) {}; + + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplay = function(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + if (!oneByOne) { + // draw all hit-detection features in "once" (by texture group) + return this.drawHitDetectionReplayAll(gl, context, + skippedFeaturesHash, featureCallback); + } else { + // draw hit-detection features one by one + return this.drawHitDetectionReplayOneByOne(gl, context, + skippedFeaturesHash, featureCallback, opt_hitExtent); + } +}; + + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplayAll = function(gl, context, skippedFeaturesHash, + featureCallback) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawReplay(gl, context, skippedFeaturesHash, true); + + var result = featureCallback(null); + if (result) { + return result; + } else { + return undefined; + } +}; + + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + var gl = context.getGL(); + + // bind the vertices buffer + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer); + + // bind the indices buffer + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer); + + var locations = this.setUpProgram(gl, context, size, pixelRatio); + + // set the "uniform" values + var projectionMatrix = ol.transform.reset(this.projectionMatrix_); + ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1])); + ol.transform.rotate(projectionMatrix, -rotation); + ol.transform.translate(projectionMatrix, -(center[0] - this.origin[0]), -(center[1] - this.origin[1])); + + var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_); + ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]); + + var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_); + if (rotation !== 0) { + ol.transform.rotate(offsetRotateMatrix, -rotation); + } + + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix)); + gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix)); + gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix)); + gl.uniform1f(locations.u_opacity, opacity); + + // draw! + var result; + if (featureCallback === undefined) { + this.drawReplay(gl, context, skippedFeaturesHash, false); + } else { + // draw feature by feature for the hit-detection + result = this.drawHitDetectionReplay(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } + + // disable the vertex attrib arrays + for (var i in locations) { + if (typeof locations[i] === 'number') { + gl.disableVertexAttribArray(locations[i]); + } + } + + if (this.lineStringReplay) { + this.lineStringReplay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } + + return result; +}; + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {number} start Start index. + * @param {number} end End index. + */ +ol.render.webgl.Replay.prototype.drawElements = function( + gl, context, start, end) { + var elementType = context.hasOESElementIndexUint ? + ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; + var elementSize = context.hasOESElementIndexUint ? 4 : 2; + + var numItems = end - start; + var offsetInBytes = start * elementSize; + gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes); +}; diff --git a/src/ol/render/webgl/replaygroup.js b/src/ol/render/webgl/replaygroup.js new file mode 100644 index 0000000000..0d82c470f4 --- /dev/null +++ b/src/ol/render/webgl/replaygroup.js @@ -0,0 +1,280 @@ +goog.provide('ol.render.webgl.ReplayGroup'); + +goog.require('ol'); +goog.require('ol.render.ReplayGroup'); +goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.render.webgl.PolygonReplay'); + +/** + * @constructor + * @extends {ol.render.ReplayGroup} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @param {number=} opt_renderBuffer Render buffer. + * @struct + */ +ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { + ol.render.ReplayGroup.call(this); + + /** + * @type {ol.Extent} + * @private + */ + this.maxExtent_ = maxExtent; + + /** + * @type {number} + * @private + */ + this.tolerance_ = tolerance; + + /** + * @type {number|undefined} + * @private + */ + this.renderBuffer_ = opt_renderBuffer; + + /** + * ImageReplay only is supported at this point. + * @type {Object.} + * @private + */ + this.replays_ = {}; + +}; +ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); + + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { + var functions = []; + var replayKey; + for (replayKey in this.replays_) { + functions.push( + this.replays_[replayKey].getDeleteResourcesFunction(context)); + } + return function() { + var length = functions.length; + var result; + for (var i = 0; i < length; i++) { + result = functions[i].apply(this, arguments); + } + return result; + }; +}; + + +/** + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.ReplayGroup.prototype.finish = function(context) { + var replayKey; + for (replayKey in this.replays_) { + this.replays_[replayKey].finish(context); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { + var replay = this.replays_[replayType]; + if (replay === undefined) { + var constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; + replay = new constructor(this.tolerance_, this.maxExtent_); + this.replays_[replayType] = replay; + } + return replay; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { + return ol.obj.isEmpty(this.replays_); +}; + + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + */ +ol.render.webgl.ReplayGroup.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash) { + var i, ii, replay; + for (i = 0, ii = ol.render.replay.ORDER.length; i < ii; ++i) { + replay = this.replays_[ol.render.replay.ORDER[i]]; + if (replay !== undefined) { + replay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + undefined, false); + } + } +}; + + +/** + * @private + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) { + var i, replay, result; + for (i = ol.render.replay.ORDER.length - 1; i >= 0; --i) { + replay = this.replays_[ol.render.replay.ORDER[i]]; + if (replay !== undefined) { + result = replay.replay(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); + if (result) { + return result; + } + } + } + return undefined; +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + callback) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + + /** + * @type {ol.Extent} + */ + var hitExtent; + if (this.renderBuffer_ !== undefined) { + // build an extent around the coordinate, so that only features that + // intersect this extent are checked + hitExtent = ol.extent.buffer( + ol.extent.createOrUpdateFromCoordinate(coordinate), + resolution * this.renderBuffer_); + } + + return this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_, + pixelRatio, opacity, skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + var result = callback(feature); + if (result) { + return result; + } + } + }, true, hitExtent); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @return {boolean} Is there a feature at the given coordinate? + */ +ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + var hasFeature = this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_, + pixelRatio, opacity, skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {boolean} Is there a feature? + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + return imageData[3] > 0; + }, false); + + return hasFeature !== undefined; +}; + +/** + * @const + * @private + * @type {Array.} + */ +ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1]; + +/** + * @const + * @private + * @type {Object.} + */ +ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_ = { + 'Image': ol.render.webgl.ImageReplay, + 'LineString': ol.render.webgl.LineStringReplay, + 'Polygon': ol.render.webgl.PolygonReplay +}; diff --git a/src/ol/render/webgl/webglpolygondefaultshader.js b/src/ol/render/webgl/webglpolygondefaultshader.js deleted file mode 100644 index 1675d265a1..0000000000 --- a/src/ol/render/webgl/webglpolygondefaultshader.js +++ /dev/null @@ -1,123 +0,0 @@ -// This file is automatically generated, do not edit -goog.provide('ol.render.webgl.polygonreplay.shader.Default'); -goog.provide('ol.render.webgl.polygonreplay.shader.Default.Locations'); -goog.provide('ol.render.webgl.polygonreplay.shader.DefaultFragment'); -goog.provide('ol.render.webgl.polygonreplay.shader.DefaultVertex'); - -goog.require('ol.webgl.shader'); - - -/** - * @constructor - * @extends {ol.webgl.shader.Fragment} - * @struct - */ -ol.render.webgl.polygonreplay.shader.DefaultFragment = function() { - ol.webgl.shader.Fragment.call(this, ol.render.webgl.polygonreplay.shader.DefaultFragment.SOURCE); -}; -ol.inherits(ol.render.webgl.polygonreplay.shader.DefaultFragment, ol.webgl.shader.Fragment); -goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultFragment); - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? - ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE : - ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE; - - -/** - * @constructor - * @extends {ol.webgl.shader.Vertex} - * @struct - */ -ol.render.webgl.polygonreplay.shader.DefaultVertex = function() { - ol.webgl.shader.Vertex.call(this, ol.render.webgl.polygonreplay.shader.DefaultVertex.SOURCE); -}; -ol.inherits(ol.render.webgl.polygonreplay.shader.DefaultVertex, ol.webgl.shader.Vertex); -goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultVertex); - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n vec4 offsets = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){vec4 offsets=c*vec4(0.,0.,0.,0.);gl_Position=b*vec4(a,0.,1.)+offsets;}'; - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? - ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE : - ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE; - - -/** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ -ol.render.webgl.polygonreplay.shader.Default.Locations = function(gl, program) { - - /** - * @type {WebGLUniformLocation} - */ - this.u_color = gl.getUniformLocation( - program, goog.DEBUG ? 'u_color' : 'e'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'd'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'c'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, goog.DEBUG ? 'u_opacity' : 'f'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'b'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'a'); -}; diff --git a/test/spec/ol/geom/flat/topologyflatgeom.test.js b/test/spec/ol/geom/flat/topologyflatgeom.test.js index dd93d83ac3..f6ad907c69 100644 --- a/test/spec/ol/geom/flat/topologyflatgeom.test.js +++ b/test/spec/ol/geom/flat/topologyflatgeom.test.js @@ -1,5 +1,7 @@ goog.provide('ol.test.geom.flat.topology'); +goog.require('ol.geom.flat.topology'); + describe('ol.geom.flat.topology', function() { describe('ol.geom.flat.topology.lineStringIsClosed', function() { @@ -29,5 +31,3 @@ describe('ol.geom.flat.topology', function() { }); }); - -goog.require('ol.geom.flat.topology'); diff --git a/test/spec/ol/render/webgl/replay.test.js b/test/spec/ol/render/webgl/replay.test.js index 7e157b5ade..decb86cc18 100644 --- a/test/spec/ol/render/webgl/replay.test.js +++ b/test/spec/ol/render/webgl/replay.test.js @@ -1,6 +1,5 @@ goog.provide('ol.test.render.webgl.Replay'); -goog.require('ol.extent'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); goog.require('ol.geom.MultiPoint'); @@ -8,6 +7,7 @@ goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.render.webgl.LineStringReplay'); goog.require('ol.render.webgl.PolygonReplay'); goog.require('ol.style.Fill'); goog.require('ol.style.Image'); @@ -107,25 +107,25 @@ describe('ol.render.webgl.ImageReplay', function() { point = new ol.geom.Point([1000, 2000]); replay.drawPoint(point, null); - expect(replay.vertices_).to.have.length(32); - expect(replay.indices_).to.have.length(6); - expect(replay.indices_[0]).to.be(0); - expect(replay.indices_[1]).to.be(1); - expect(replay.indices_[2]).to.be(2); - expect(replay.indices_[3]).to.be(0); - expect(replay.indices_[4]).to.be(2); - expect(replay.indices_[5]).to.be(3); + expect(replay.vertices).to.have.length(32); + expect(replay.indices).to.have.length(6); + expect(replay.indices[0]).to.be(0); + expect(replay.indices[1]).to.be(1); + expect(replay.indices[2]).to.be(2); + expect(replay.indices[3]).to.be(0); + expect(replay.indices[4]).to.be(2); + expect(replay.indices[5]).to.be(3); point = new ol.geom.Point([2000, 3000]); replay.drawPoint(point, null); - expect(replay.vertices_).to.have.length(64); - expect(replay.indices_).to.have.length(12); - expect(replay.indices_[6]).to.be(4); - expect(replay.indices_[7]).to.be(5); - expect(replay.indices_[8]).to.be(6); - expect(replay.indices_[9]).to.be(4); - expect(replay.indices_[10]).to.be(6); - expect(replay.indices_[11]).to.be(7); + expect(replay.vertices).to.have.length(64); + expect(replay.indices).to.have.length(12); + expect(replay.indices[6]).to.be(4); + expect(replay.indices[7]).to.be(5); + expect(replay.indices[8]).to.be(6); + expect(replay.indices[9]).to.be(4); + expect(replay.indices[10]).to.be(6); + expect(replay.indices[11]).to.be(7); }); }); @@ -141,38 +141,38 @@ describe('ol.render.webgl.ImageReplay', function() { multiPoint = new ol.geom.MultiPoint( [[1000, 2000], [2000, 3000]]); replay.drawMultiPoint(multiPoint, null); - expect(replay.vertices_).to.have.length(64); - expect(replay.indices_).to.have.length(12); - expect(replay.indices_[0]).to.be(0); - expect(replay.indices_[1]).to.be(1); - expect(replay.indices_[2]).to.be(2); - expect(replay.indices_[3]).to.be(0); - expect(replay.indices_[4]).to.be(2); - expect(replay.indices_[5]).to.be(3); - expect(replay.indices_[6]).to.be(4); - expect(replay.indices_[7]).to.be(5); - expect(replay.indices_[8]).to.be(6); - expect(replay.indices_[9]).to.be(4); - expect(replay.indices_[10]).to.be(6); - expect(replay.indices_[11]).to.be(7); + expect(replay.vertices).to.have.length(64); + expect(replay.indices).to.have.length(12); + expect(replay.indices[0]).to.be(0); + expect(replay.indices[1]).to.be(1); + expect(replay.indices[2]).to.be(2); + expect(replay.indices[3]).to.be(0); + expect(replay.indices[4]).to.be(2); + expect(replay.indices[5]).to.be(3); + expect(replay.indices[6]).to.be(4); + expect(replay.indices[7]).to.be(5); + expect(replay.indices[8]).to.be(6); + expect(replay.indices[9]).to.be(4); + expect(replay.indices[10]).to.be(6); + expect(replay.indices[11]).to.be(7); multiPoint = new ol.geom.MultiPoint( [[3000, 4000], [4000, 5000]]); replay.drawMultiPoint(multiPoint, null); - expect(replay.vertices_).to.have.length(128); - expect(replay.indices_).to.have.length(24); - expect(replay.indices_[12]).to.be(8); - expect(replay.indices_[13]).to.be(9); - expect(replay.indices_[14]).to.be(10); - expect(replay.indices_[15]).to.be(8); - expect(replay.indices_[16]).to.be(10); - expect(replay.indices_[17]).to.be(11); - expect(replay.indices_[18]).to.be(12); - expect(replay.indices_[19]).to.be(13); - expect(replay.indices_[20]).to.be(14); - expect(replay.indices_[21]).to.be(12); - expect(replay.indices_[22]).to.be(14); - expect(replay.indices_[23]).to.be(15); + expect(replay.vertices).to.have.length(128); + expect(replay.indices).to.have.length(24); + expect(replay.indices[12]).to.be(8); + expect(replay.indices[13]).to.be(9); + expect(replay.indices[14]).to.be(10); + expect(replay.indices[15]).to.be(8); + expect(replay.indices[16]).to.be(10); + expect(replay.indices[17]).to.be(11); + expect(replay.indices[18]).to.be(12); + expect(replay.indices[19]).to.be(13); + expect(replay.indices[20]).to.be(14); + expect(replay.indices[21]).to.be(12); + expect(replay.indices[22]).to.be(14); + expect(replay.indices[23]).to.be(15); }); }); }); @@ -230,20 +230,20 @@ describe('ol.render.webgl.LineStringReplay', function() { [[1000, 2000], [2000, 3000]]); replay.setFillStrokeStyle(null, strokeStyle1); replay.drawLineString(linestring, null); - expect(replay.vertices_).to.have.length(56); - expect(replay.indices_).to.have.length(18); + expect(replay.vertices).to.have.length(56); + expect(replay.indices).to.have.length(18); expect(replay.state_.changed).to.be(false); - expect(replay.startIndices_).to.have.length(1); - expect(replay.startIndicesFeature_).to.have.length(1); + expect(replay.startIndices).to.have.length(1); + expect(replay.startIndicesFeature).to.have.length(1); linestring = new ol.geom.LineString( [[1000, 3000], [2000, 4000], [3000, 3000]]); replay.drawLineString(linestring, null); - expect(replay.vertices_).to.have.length(140); - expect(replay.indices_).to.have.length(48); + expect(replay.vertices).to.have.length(140); + expect(replay.indices).to.have.length(48); expect(replay.state_.changed).to.be(false); - expect(replay.startIndices_).to.have.length(2); - expect(replay.startIndicesFeature_).to.have.length(2); + expect(replay.startIndices).to.have.length(2); + expect(replay.startIndicesFeature).to.have.length(2); }); }); @@ -257,11 +257,11 @@ describe('ol.render.webgl.LineStringReplay', function() { [[1000, 3000], [2000, 4000], [3000, 3000]]]); replay.setFillStrokeStyle(null, strokeStyle1); replay.drawMultiLineString(multilinestring, null); - expect(replay.vertices_).to.have.length(140); - expect(replay.indices_).to.have.length(48); + expect(replay.vertices).to.have.length(140); + expect(replay.indices).to.have.length(48); expect(replay.state_.changed).to.be(false); - expect(replay.startIndices_).to.have.length(1); - expect(replay.startIndicesFeature_).to.have.length(1); + expect(replay.startIndices).to.have.length(1); + expect(replay.startIndicesFeature).to.have.length(1); }); }); @@ -282,7 +282,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [2, 0, 1, 4, 2, 1, 2, 4, 3, 5, 3, 4, 4, 6, 5]); @@ -302,7 +302,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [2, 0, 1, 4, 2, 1, 2, 4, 3, 3, 5, 2, 6, 3, 4, 4, 7, 6]); @@ -321,7 +321,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [2, 0, 1, 1, 3, 2, 4, 2, 3, 6, 4, 3, 4, 6, 5, 5, 7, 4, @@ -344,7 +344,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [2, 0, 1, 4, 2, 0, 2, 4, 3, 5, 3, 4, 4, 6, 5]); @@ -365,17 +365,17 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [0, 2, 1, 3, 1, 2, 5, 3, 2, 3, 5, 4, 6, 4, 5, 8, 6, 5, 6, 8, 7, 9, 7, 8, 10, 9, 8]); - expect(replay.vertices_.slice(0, 7)).to.eql( - replay.vertices_.slice(-14, -7)); - expect(replay.vertices_.slice(14, 21)).to.eql( - replay.vertices_.slice(-7)); + expect(replay.vertices.slice(0, 7)).to.eql( + replay.vertices.slice(-14, -7)); + expect(replay.vertices.slice(14, 21)).to.eql( + replay.vertices.slice(-7)); }); }); }); From 5cf2e9c07206a0f01c87de504a541331b158a872 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 5 Oct 2016 10:14:30 +0200 Subject: [PATCH 56/88] Add shutdown methods to replays --- src/ol/render/webgl/imagereplay/index.js | 12 ++++++++++++ src/ol/render/webgl/linestringreplay/index.js | 11 +++++++++++ src/ol/render/webgl/polygonreplay/index.js | 8 ++++++++ src/ol/render/webgl/replay.js | 17 ++++++++++++----- 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 402546cf16..8a67843502 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -449,6 +449,18 @@ ol.render.webgl.ImageReplay.prototype.setUpProgram = function(gl, context, size, }; +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.shutDownProgram_ = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_offsets); + gl.disableVertexAttribArray(locations.a_texCoord); + gl.disableVertexAttribArray(locations.a_opacity); + gl.disableVertexAttribArray(locations.a_rotateWithView); +}; + + /** * @inheritDoc */ diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js index 498121129b..70c694ece6 100644 --- a/src/ol/render/webgl/linestringreplay/index.js +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -472,6 +472,17 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram = function(gl, context, }; +/** + * @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 */ diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js index 427fd3e597..eaf89779cf 100644 --- a/src/ol/render/webgl/polygonreplay/index.js +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -848,6 +848,14 @@ ol.render.webgl.PolygonReplay.prototype.setUpProgram = function(gl, context, siz }; +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.shutDownProgram_ = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); +}; + + /** * @inheritDoc */ diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js index 7caed3e343..71b228388c 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -143,6 +143,17 @@ ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixe * @abstract * @protected * @param {WebGLRenderingContext} gl gl. + * @param {ol.render.webgl.imagereplay.defaultshader.Locations| + ol.render.webgl.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations} locations Locations. + */ +ol.render.webgl.Replay.prototype.shutDownProgram_ = function(gl, locations) {}; + + +/** + * @abstract + * @private + * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. * @param {Object.} skippedFeaturesHash Ids of features * to skip. @@ -286,11 +297,7 @@ ol.render.webgl.Replay.prototype.replay = function(context, } // disable the vertex attrib arrays - for (var i in locations) { - if (typeof locations[i] === 'number') { - gl.disableVertexAttribArray(locations[i]); - } - } + this.shutDownProgram_(gl, locations); if (this.lineStringReplay) { this.lineStringReplay.replay(context, From 8e8098b3621c4d904a8241d88a754cafdb1c4c33 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 5 Oct 2016 18:34:54 +0200 Subject: [PATCH 57/88] Add some test to PolygonReplay --- test/spec/ol/render/webgl/replay.test.js | 62 +++++++----------- .../polygon-fill-and-strokes-webgl.png | Bin 0 -> 458 bytes .../expected/polygon-types-webgl-stroke.png | Bin 0 -> 780 bytes .../ol/style/expected/polygon-types-webgl.png | Bin 0 -> 276 bytes .../style/expected/polygon-zindex-webgl.png | Bin 0 -> 300 bytes test_rendering/spec/ol/style/polygon.test.js | 32 ++++++++- 6 files changed, 53 insertions(+), 41 deletions(-) create mode 100644 test_rendering/spec/ol/style/expected/polygon-fill-and-strokes-webgl.png create mode 100644 test_rendering/spec/ol/style/expected/polygon-types-webgl-stroke.png create mode 100644 test_rendering/spec/ol/style/expected/polygon-types-webgl.png create mode 100644 test_rendering/spec/ol/style/expected/polygon-zindex-webgl.png diff --git a/test/spec/ol/render/webgl/replay.test.js b/test/spec/ol/render/webgl/replay.test.js index decb86cc18..1456bb2691 100644 --- a/test/spec/ol/render/webgl/replay.test.js +++ b/test/spec/ol/render/webgl/replay.test.js @@ -397,7 +397,7 @@ describe('ol.render.webgl.PolygonReplay', function() { replay = new ol.render.webgl.PolygonReplay(tolerance, maxExtent); }); - describe('#drawPolygonGeometry', function() { + describe('#drawPolygon', function() { beforeEach(function() { replay.setFillStrokeStyle(fillStyle, strokeStyle); }); @@ -406,44 +406,30 @@ describe('ol.render.webgl.PolygonReplay', function() { var polygon1 = new ol.geom.Polygon( [[[1000, 2000], [1200, 2000], [1200, 3000]]] ); - replay.drawPolygonGeometry(polygon1, null); - expect(replay.vertices_).to.have.length(18); - expect(replay.indices_).to.have.length(3); + replay.drawPolygon(polygon1, null); + expect(replay.vertices).to.have.length(6); + expect(replay.indices).to.have.length(3); - expect(replay.vertices_).to.eql([ - 1200, 2000, 0, 0, 1, 0.5, - 1200, 3000, 0, 0, 1, 0.5, - 1000, 2000, 0, 0, 1, 0.5]); - expect(replay.indices_).to.eql([0, 1, 2]); - - expect(replay.lineStringReplay_.vertices_).to.have.length(24); - expect(replay.lineStringReplay_.vertices_).to.eql([ - 1000, 2000, 0, 1, 0, 0.4, - 1200, 2000, 0, 1, 0, 0.4, - 1200, 2000, 0, 1, 0, 0.4, - 1200, 3000, 0, 1, 0, 0.4 - ]); + expect(replay.vertices).to.eql([ + 1200, 3000, 1200, 2000, 1000, 2000]); + expect(replay.indices).to.eql([2, 0, 1]); var polygon2 = new ol.geom.Polygon( [[[4000, 2000], [4200, 2000], [4200, 3000]]] ); - replay.drawPolygonGeometry(polygon2, null); - expect(replay.vertices_).to.have.length(36); - expect(replay.indices_).to.have.length(6); + replay.drawPolygon(polygon2, null); + expect(replay.vertices).to.have.length(12); + expect(replay.indices).to.have.length(6); - expect(replay.vertices_).to.eql([ - 1200, 2000, 0, 0, 1, 0.5, - 1200, 3000, 0, 0, 1, 0.5, - 1000, 2000, 0, 0, 1, 0.5, - 4200, 2000, 0, 0, 1, 0.5, - 4200, 3000, 0, 0, 1, 0.5, - 4000, 2000, 0, 0, 1, 0.5 + expect(replay.vertices).to.eql([ + 1200, 3000, 1200, 2000, 1000, 2000, + 4200, 3000, 4200, 2000, 4000, 2000 ]); - expect(replay.indices_).to.eql([0, 1, 2, 3, 4, 5]); + expect(replay.indices).to.eql([2, 0, 1, 5, 3, 4]); }); }); - describe('#drawMultiPolygonGeometry', function() { + describe('#drawMultiPolygon', function() { beforeEach(function() { replay.setFillStrokeStyle(fillStyle, strokeStyle); }); @@ -453,19 +439,15 @@ describe('ol.render.webgl.PolygonReplay', function() { [[[1000, 2000], [1200, 2000], [1200, 3000]]], [[[4000, 2000], [4200, 2000], [4200, 3000]]] ]); - replay.drawMultiPolygonGeometry(multiPolygon, null); - expect(replay.vertices_).to.have.length(36); - expect(replay.indices_).to.have.length(6); + replay.drawMultiPolygon(multiPolygon, null); + expect(replay.vertices).to.have.length(12); + expect(replay.indices).to.have.length(6); - expect(replay.vertices_).to.eql([ - 1200, 2000, 0, 0, 1, 0.5, - 1200, 3000, 0, 0, 1, 0.5, - 1000, 2000, 0, 0, 1, 0.5, - 4200, 2000, 0, 0, 1, 0.5, - 4200, 3000, 0, 0, 1, 0.5, - 4000, 2000, 0, 0, 1, 0.5 + expect(replay.vertices).to.eql([ + 1200, 3000, 1200, 2000, 1000, 2000, + 4200, 3000, 4200, 2000, 4000, 2000 ]); - expect(replay.indices_).to.eql([0, 1, 2, 3, 4, 5]); + expect(replay.indices).to.eql([2, 0, 1, 5, 3, 4]); }); }); }); diff --git a/test_rendering/spec/ol/style/expected/polygon-fill-and-strokes-webgl.png b/test_rendering/spec/ol/style/expected/polygon-fill-and-strokes-webgl.png new file mode 100644 index 0000000000000000000000000000000000000000..da2f9000f4ab0a470201e5c6d6b6ec8024ebe752 GIT binary patch literal 458 zcmV;*0X6=KP)>%% zQXyPw*LEUgz07y-{NCNWIN>akheyJLfrxD{dnN=q z7w+{s^TLWm!o`ivL6*hrzLdtv(E=uuSopJ9RZPmEkZ|> zn6=q4S{N-|0eA!+3FwQTP#7(Y7Dj8i0`Let0*}BWngV}*v*Q{c&@WiOQLf*B6XFf! z^*hMBdO!dtA_42S3T6DK+UjXMih+oSc#Wif1ATM9LW;T0ivR!s07*qoM6N<$f+ao7 A!T-z=CDS_zx_|=l|A9B zy=vop+Ryast@0~b8M$@e1c{c4k5BJ!Klv|Qj``<~Dx-ZL-97k!>83yuBO$?h43r_Mi znZd8<*l_b9$4ND*Gwl-;4!kL7NfG9KHd$O~&i$8%FPFbst}eyWfRJ$Z5J)JqaN5+# z_E}Md!@&BE?h~^O-`%IZQCeH`r}(1Mm72d>oW#y)&HbcZ|H}H#+v+~1kQNj3q?-ko zodg1(J(*V#d}g9<3kRp8+u9!0-M_brD{{P4R50U`Z4xkKN_u)hkwZ{fAM2rkNcJ?EpcuCzeGlnBU4GijHh3x#fIa<(tUe{mRKK&zVohe_QqfCpVLi)k9k(U zo?ay~+E!)tBheF%K98<*kH7wMZhdrn1-H|QE{76Ux5t7_ z0@=JivCk)(wyxFVP%O<^Zls*T6&)+!G$&{6*`-sD{od9g(Ea4ImD4C#Oc(9bEjET03=7jv!=$$8BLRIF|AY>&Y>b8>VYQJkGZCafLN7POI ztImEPx(C1Q96`wZe2{VsT5g19{m963GdthdZl`brN8C)k`hgZ`f!6s3$Osup&d7{U z$1BVk`=9+S89|7E8IG*{X?!yZT4`tr^ITqHd1_rgrEr5SAwvJS66v2&+|+}C5R&Fn a8Al!(wt6$}x+3=g0000%hAsNnZXKds>WFX=)mv_ZP z2PNeT7nIl%#nKW5w4QmWOjvP~!AdIW=rcWz+PJLyPROmUHzh~u z`qMWLeQFAK`peyTz$S4rp~b_s{(pRS?zY!mHOHpk>r;Qt7~sRAENLNdl0|s;);6E> z->qNg*4ur_zr7{*|3}6Eog*I0mMaDszgd``aw6vX+BNJ4!rNq(AJ30nFDl^a`MX;v z%}r}p+NY&Q&3`}1SITP;G<4W4qx8+=Xp?{-^OnV2CnqpAq~G P=zRuHS3j3^P6 Date: Thu, 6 Oct 2016 12:54:46 +0200 Subject: [PATCH 58/88] Add zIndex support to WebGL renderer --- src/ol/render/webgl/replaygroup.js | 98 ++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 32 deletions(-) diff --git a/src/ol/render/webgl/replaygroup.js b/src/ol/render/webgl/replaygroup.js index 0d82c470f4..30ff9761ef 100644 --- a/src/ol/render/webgl/replaygroup.js +++ b/src/ol/render/webgl/replaygroup.js @@ -1,6 +1,7 @@ goog.provide('ol.render.webgl.ReplayGroup'); goog.require('ol'); +goog.require('ol.array'); goog.require('ol.render.ReplayGroup'); goog.require('ol.render.webgl'); goog.require('ol.render.webgl.ImageReplay'); @@ -37,11 +38,11 @@ ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { this.renderBuffer_ = opt_renderBuffer; /** - * ImageReplay only is supported at this point. - * @type {Object.} * @private + * @type {!Object.>} */ - this.replays_ = {}; + this.replaysByZIndex_ = {}; }; ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); @@ -53,10 +54,14 @@ ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); */ ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { var functions = []; - var replayKey; - for (replayKey in this.replays_) { - functions.push( - this.replays_[replayKey].getDeleteResourcesFunction(context)); + 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; @@ -73,9 +78,13 @@ ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(cont * @param {ol.webgl.Context} context Context. */ ol.render.webgl.ReplayGroup.prototype.finish = function(context) { - var replayKey; - for (replayKey in this.replays_) { - this.replays_[replayKey].finish(context); + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + replays[replayKey].finish(context); + } } }; @@ -84,11 +93,20 @@ ol.render.webgl.ReplayGroup.prototype.finish = function(context) { * @inheritDoc */ ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { - var replay = this.replays_[replayType]; + var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; + var replays = this.replaysByZIndex_[zIndexKey]; + if (replays === undefined) { + replays = {}; + this.replaysByZIndex_[zIndexKey] = replays; + } + var replay = replays[replayType]; if (replay === undefined) { - var constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; - replay = new constructor(this.tolerance_, this.maxExtent_); - this.replays_[replayType] = replay; + var Constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; + ol.DEBUG && console.assert(Constructor !== undefined, + replayType + + ' constructor missing from ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_'); + replay = new Constructor(this.tolerance_, this.maxExtent_); + replays[replayType] = replay; } return replay; }; @@ -98,7 +116,7 @@ ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { * @inheritDoc */ ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { - return ol.obj.isEmpty(this.replays_); + return ol.obj.isEmpty(this.replaysByZIndex_); }; @@ -116,14 +134,21 @@ ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { ol.render.webgl.ReplayGroup.prototype.replay = function(context, center, resolution, rotation, size, pixelRatio, opacity, skippedFeaturesHash) { - var i, ii, replay; - for (i = 0, ii = ol.render.replay.ORDER.length; i < ii; ++i) { - replay = this.replays_[ol.render.replay.ORDER[i]]; - if (replay !== undefined) { - replay.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - undefined, false); + /** @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); + } } } }; @@ -150,15 +175,24 @@ ol.render.webgl.ReplayGroup.prototype.replay = function(context, ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, center, resolution, rotation, size, pixelRatio, opacity, skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) { - var i, replay, result; - for (i = ol.render.replay.ORDER.length - 1; i >= 0; --i) { - replay = this.replays_[ol.render.replay.ORDER[i]]; - if (replay !== undefined) { - result = replay.replay(context, - center, resolution, rotation, size, pixelRatio, opacity, - skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); - if (result) { - return result; + /** @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; + } } } } From 0c3d5eca5cdc07a7deadde908b3f5b84ccb6c94b Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Sun, 9 Oct 2016 12:46:58 +0200 Subject: [PATCH 59/88] Add tests for the linked list struct --- test/spec/ol/structs/linkedlist.test.js | 267 ++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 test/spec/ol/structs/linkedlist.test.js diff --git a/test/spec/ol/structs/linkedlist.test.js b/test/spec/ol/structs/linkedlist.test.js new file mode 100644 index 0000000000..661fdd66cf --- /dev/null +++ b/test/spec/ol/structs/linkedlist.test.js @@ -0,0 +1,267 @@ +goog.provide('ol.test.structs.LinkedList'); + +goog.require('ol.structs.LinkedList'); + +describe('ol.structs.LinkedList', function() { + var ll; + var item = {}; + var item2 = {}; + beforeEach(function() { + ll = new ol.structs.LinkedList(); + }); + + it('defaults to circular', function() { + expect(ll.circular_).to.be(true); + }); + + it('creates an empty list', function() { + expect(ll.length_).to.be(0); + expect(ll.first_).to.be(undefined); + expect(ll.last_).to.be(undefined); + expect(ll.head_).to.be(undefined); + }); + + describe('#insertItem', function() { + beforeEach(function() { + ll.insertItem(item); + }); + + it('inserts an item into the list', function() { + expect(ll.length_).to.be(1); + }); + + it('sets the cursor to the inserted item', function() { + expect(ll.head_.data).to.be(item); + }); + + it('links the previous item to the new one', function() { + ll.insertItem(item2); + expect(ll.head_.prev.data).to.be(item); + expect(ll.head_.prev.next.data).to.be(item2); + }); + }); + + describe('#removeItem', function() { + var item3 = {}; + beforeEach(function() { + ll.insertItem(item); + ll.insertItem(item2); + ll.insertItem(item3); + }); + + it('removes the current item', function() { + ll.removeItem(); + expect(ll.length_).to.be(2); + expect(ll.head_.data).not.to.be(item3); + }); + + it('sets the cursor to the next item if possible', function() { + ll.removeItem(); + expect(ll.head_.data).to.be(item); + }); + + it('otherwise sets the cursor to the prevous item', function() { + ll = new ol.structs.LinkedList(false); + ll.insertItem(item); + ll.insertItem(item2); + ll.insertItem(item3); + ll.removeItem(); + expect(ll.head_.data).to.be(item2); + }); + + it('empties a list with only one item', function() { + ll = new ol.structs.LinkedList(); + ll.insertItem(item); + ll.removeItem(); + expect(ll.length_).to.be(0); + expect(ll.head_).to.be(undefined); + expect(ll.first_).to.be(undefined); + expect(ll.last_).to.be(undefined); + }); + }); + + describe('#firstItem', function() { + it('sets the cursor to the first item and returns its data', function() { + ll.insertItem(item); + ll.insertItem(item2); + var i = ll.firstItem(); + expect(i).to.be(item); + expect(ll.head_.data).to.be(item); + }); + + it('returns undefined on empty list', function() { + var i = ll.firstItem(); + expect(i).to.be(undefined); + }); + }); + + describe('#lastItem', function() { + it('sets the cursor to the last item and returns its data', function() { + ll.insertItem(item); + ll.insertItem(item2); + ll.firstItem(); + var i = ll.lastItem(); + expect(i).to.be(item2); + expect(ll.head_.data).to.be(item2); + }); + + it('returns undefined on empty list', function() { + var i = ll.lastItem(); + expect(i).to.be(undefined); + }); + }); + + describe('#nextItem', function() { + it('sets the cursor to the next item and returns its data', function() { + ll.insertItem(item); + ll.insertItem(item2); + ll.firstItem(); + var i = ll.nextItem(); + expect(i).to.be(item2); + expect(ll.head_.data).to.be(item2); + }); + + it('returns undefined on empty list', function() { + var i = ll.nextItem(); + expect(i).to.be(undefined); + }); + }); + + describe('#prevItem', function() { + it('sets the cursor to the previous item and returns its data', function() { + ll.insertItem(item); + ll.insertItem(item2); + var i = ll.prevItem(); + expect(i).to.be(item); + expect(ll.head_.data).to.be(item); + }); + + it('returns undefined on empty list', function() { + var i = ll.prevItem(); + expect(i).to.be(undefined); + }); + }); + + describe('#getNextItem', function() { + it('returns the data of the next item without stepping the cursor', function() { + ll.insertItem(item); + ll.insertItem(item2); + ll.firstItem(); + var i = ll.getNextItem(); + expect(i).to.be(item2); + expect(ll.head_.data).to.be(item); + }); + + it('returns undefined on empty list', function() { + var i = ll.getNextItem(); + expect(i).to.be(undefined); + }); + }); + + describe('#getPrevItem', function() { + it('returns the data of the previous item without stepping the cursor', function() { + ll.insertItem(item); + ll.insertItem(item2); + var i = ll.getPrevItem(); + expect(i).to.be(item); + expect(ll.head_.data).to.be(item2); + }); + + it('returns undefined on empty list', function() { + var i = ll.getPrevItem(); + expect(i).to.be(undefined); + }); + }); + + describe('#getCurrItem', function() { + it('returns the data of the current item', function() { + var item3 = {}; + ll.insertItem(item); + ll.insertItem(item2); + ll.insertItem(item3); + ll.prevItem(); + var i = ll.getCurrItem(); + expect(i).to.be(item2); + expect(ll.head_.data).to.be(item2); + }); + + it('returns undefined on empty list', function() { + var i = ll.getCurrItem(); + expect(i).to.be(undefined); + }); + }); + + describe('#getLength', function() { + it('returns the length of the list', function() { + ll.insertItem(item); + ll.insertItem(item2); + var l = ll.getLength(); + expect(l).to.be(2); + }); + }); + + describe('#concat', function() { + var ll2, item3; + beforeEach(function() { + item3 = {}; + ll2 = new ol.structs.LinkedList(); + ll2.insertItem(item); + ll2.insertItem(item2); + ll2.insertItem(item3); + }); + + it('concatenates a second list with the current one', function() { + var item4 = {}; + var item5 = {}; + var item6 = {}; + ll.insertItem(item4); + ll.insertItem(item5); + ll.insertItem(item6); + ll.prevItem(); + ll.concat(ll2); + expect(ll.length_).to.be(6); + expect(ll.head_.data).to.be(item5); + expect(ll.head_.next.data).to.be(item); + expect(ll.head_.next.next.next.next.data).to.be(item6); + }); + + it('receives the second list if the current one is empty', function() { + ll.concat(ll2); + expect(ll.length_).to.be(3); + expect(ll.first_.data).to.be(item); + expect(ll.last_.data).to.be(item3); + expect(ll.head_.data).to.be(item3); + }); + + it('destroys the second list', function() { + ll.concat(ll2); + expect(ll2.length_).to.be(0); + expect(ll2.first_).to.be(undefined); + expect(ll2.last_).to.be(undefined); + expect(ll2.head_).to.be(undefined); + }); + }); + + describe('when circular', function() { + beforeEach(function() { + ll = new ol.structs.LinkedList(); + ll.insertItem(item); + }); + + describe('#insertItem', function() { + it('initializes the list in a circular way', function() { + expect(ll.head_.prev.data).to.be(item); + expect(ll.head_.next.data).to.be(item); + }); + }); + + describe('#setFirstItem', function() { + it('resets the first item to the current one', function() { + ll.insertItem(item2); + ll.setFirstItem(); + expect(ll.first_.data).to.be(item2); + expect(ll.last_.data).to.be(item); + }); + }); + }); +}); From 004c0f7e271dd14404c3e01ab286a776607e1c99 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 10 Oct 2016 13:18:39 +0200 Subject: [PATCH 60/88] Fix WebGL PolygonReplay#getPointsInTriangle_ --- src/ol/render/webgl/polygonreplay/index.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js index eaf89779cf..9516c13bdd 100644 --- a/src/ol/render/webgl/polygonreplay/index.js +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -580,11 +580,13 @@ ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt * @param {ol.structs.RBush} rtree R-Tree of the polygon. */ ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) { - list.removeItem(); - s0.p1 = s1.p1; - rtree.remove(s1); - rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + 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); + } }; @@ -607,7 +609,7 @@ ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { for (j in segmentsInExtent[i]) { p = segmentsInExtent[i][j]; - if (p.x && p.y && (!opt_reflex || p.reflex)) { + if (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, From 3af5c2805e46e4002b5be50676d7969632c3704c Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 10 Oct 2016 14:39:47 +0200 Subject: [PATCH 61/88] Add tests to WebGL triangulating functions --- test/spec/ol/render/webgl/replay.test.js | 240 +++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/test/spec/ol/render/webgl/replay.test.js b/test/spec/ol/render/webgl/replay.test.js index 1456bb2691..1e5b866934 100644 --- a/test/spec/ol/render/webgl/replay.test.js +++ b/test/spec/ol/render/webgl/replay.test.js @@ -9,6 +9,8 @@ goog.require('ol.geom.Polygon'); goog.require('ol.render.webgl.ImageReplay'); goog.require('ol.render.webgl.LineStringReplay'); goog.require('ol.render.webgl.PolygonReplay'); +goog.require('ol.structs.LinkedList'); +goog.require('ol.structs.RBush'); goog.require('ol.style.Fill'); goog.require('ol.style.Image'); goog.require('ol.style.Stroke'); @@ -450,4 +452,242 @@ describe('ol.render.webgl.PolygonReplay', function() { expect(replay.indices).to.eql([2, 0, 1, 5, 3, 4]); }); }); + + describe('triangulating functions', function() { + var list, rtree; + beforeEach(function() { + list = new ol.structs.LinkedList(); + rtree = new ol.structs.RBush(); + }); + + describe('#createPoint_', function() { + it('creates a WebGL polygon vertex', function() { + var p = replay.createPoint_(1, 1, 1); + expect(p.x).to.be(1); + expect(p.y).to.be(1); + expect(p.i).to.be(1); + expect(p.reflex).to.be(undefined); + }); + + it('adds the point to the vertex array', function() { + replay.createPoint_(1, 1, 1); + expect(replay.vertices.length).to.be(2); + expect(replay.vertices[0]).to.be(1); + expect(replay.vertices[1]).to.be(1); + }); + }); + + describe('#insertItem_', function() { + var p0, p1; + beforeEach(function() { + p0 = replay.createPoint_(1, 1, 1); + p1 = replay.createPoint_(2, 2, 2); + }); + + it('creates a WebGL polygon segment', function() { + var seg = replay.insertItem_(p0, p1, list, rtree); + expect(seg.p0).to.be(p0); + expect(seg.p1).to.be(p1); + }); + + it('inserts the segment into the provided linked list', function() { + var seg = replay.insertItem_(p0, p1, list, rtree); + expect(list.head_.data).to.be(seg); + }); + + it('inserts the segment into the R-Tree, if provided', function() { + replay.insertItem_(p0, p1, list); + expect(rtree.isEmpty()).to.be(true); + replay.insertItem_(p0, p1, list, rtree); + expect(rtree.isEmpty()).to.be(false); + }); + }); + + describe('#removeItem_', function() { + var s0, s1; + beforeEach(function() { + var p = replay.createPoint_(2, 2, 2); + s0 = replay.insertItem_(replay.createPoint_(1, 1, 1), + p, list, rtree); + s1 = replay.insertItem_(p, + replay.createPoint_(5, 2, 3), list, rtree); + }); + + it('removes the current item', function() { + replay.removeItem_(s0, s1, list, rtree); + expect(list.head_.data).not.to.be(s1); + expect(rtree.getAll().length).to.be(1); + }); + + it('updates the preceding segment', function() { + var dataExtent = rtree.getExtent(); + replay.removeItem_(s0, s1, list, rtree); + expect(s0.p1).to.be(s1.p1); + expect(rtree.getExtent()).to.eql(dataExtent); + }); + }); + + describe('#getPointsInTriangle_', function() { + var p0, p1, p2, p3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + replay.insertItem_(p0, p1, list, rtree); + replay.insertItem_(p1, p2, list, rtree); + replay.insertItem_(p2, p3, list, rtree); + replay.insertItem_(p3, p0, list, rtree); + replay.classifyPoints_(list, rtree, false); + }); + + it('gets every point in a triangle', function() { + var points = replay.getPointsInTriangle_({x: -3, y: 6}, {x: 7, y: 6}, + {x: 2, y: 2}, rtree); + expect(points).to.eql([p1, p2, p3]); + }); + + it('gets only reflex points in a triangle', function() { + var points = replay.getPointsInTriangle_({x: -3, y: 6}, {x: 7, y: 6}, + {x: 2, y: 2}, rtree, true); + expect(points).to.eql([p2]); + }); + }); + + describe('#getIntersections_', function() { + var p0, p1, p2, p3, s0, s1, s2, s3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + s0 = replay.insertItem_(p0, p1, list, rtree); + s1 = replay.insertItem_(p1, p2, list, rtree); + s2 = replay.insertItem_(p2, p3, list, rtree); + s3 = replay.insertItem_(p3, p0, list, rtree); + }); + + it('gets intersecting, but non touching segments', function() { + var segments = replay.getIntersections_({p0: {x: 0, y: 3}, p1: {x: 4, y: 5}}, + rtree); + expect(segments).to.eql([s0, s1]); + }); + + it('gets intersecting and touching segments', function() { + var segments = replay.getIntersections_({p0: {x: 0, y: 3}, p1: {x: 4, y: 5}}, + rtree, true); + expect(segments).to.eql([s0, s1, s2, s3]); + }); + }); + + describe('#calculateIntersection_', function() { + var p0 = {x: 0, y: 0}; + var p1 = {x: 4, y: 4}; + var p2 = {x: 0, y: 4}; + var p3 = {x: 4, y: 0}; + + it('calculates the intersection point of two intersecting segments', function() { + var i = replay.calculateIntersection_(p0, p1, p2, p3); + var t = replay.calculateIntersection_(p0, p1, p1, p2); + expect(i).to.eql([2, 2]); + expect(t).to.be(undefined); + }); + + it('calculates the intersection point of two touching segments', function() { + var t = replay.calculateIntersection_(p0, p1, p1, p2, true); + expect(t).to.eql([4, 4]); + }); + }); + + describe('#diagonalIsInside_', function() { + var p0, p1, p2, p3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + replay.insertItem_(p0, p1, list, rtree); + replay.insertItem_(p1, p2, list, rtree); + replay.insertItem_(p2, p3, list, rtree); + replay.insertItem_(p3, p0, list, rtree); + replay.classifyPoints_(list, rtree, false); + }); + + it('identifies if diagonal is inside the polygon', function() { + var inside = replay.diagonalIsInside_(p1, p2, p3, p0, p1); + expect(inside).to.be(true); + }); + + it('identifies if diagonal is outside the polygon', function() { + var inside = replay.diagonalIsInside_(p0, p1, p2, p3, p0); + expect(inside).to.be(false); + }); + }); + + describe('#classifyPoints_', function() { + var p0, p1, p2, p3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + replay.insertItem_(p0, p1, list, rtree); + replay.insertItem_(p1, p2, list, rtree); + replay.insertItem_(p2, p3, list, rtree); + replay.insertItem_(p3, p0, list, rtree); + }); + + it('classifies the points of clockwise polygons', function() { + replay.classifyPoints_(list, rtree, false); + expect(p0.reflex).to.be(false); + expect(p1.reflex).to.be(false); + expect(p2.reflex).to.be(true); + expect(p3.reflex).to.be(false); + }); + + it('classifies the points of counter-clockwise polygons', function() { + replay.classifyPoints_(list, rtree, true); + expect(p0.reflex).to.be(true); + expect(p1.reflex).to.be(true); + expect(p2.reflex).to.be(false); + expect(p3.reflex).to.be(true); + }); + + it('removes collinear points', function() { + replay.insertItem_(p3, p0, list, rtree); + replay.classifyPoints_(list, rtree, false); + expect(list.getLength()).to.be(4); + expect(rtree.getAll().length).to.be(4); + }); + }); + + describe('#isSimple_', function() { + var p0, p1, p2, p3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + replay.insertItem_(p0, p1, list, rtree); + replay.insertItem_(p1, p2, list, rtree); + replay.insertItem_(p2, p3, list, rtree); + replay.insertItem_(p3, p0, list, rtree); + }); + + it('identifies simple polygons', function() { + var simple = replay.isSimple_(list, rtree); + expect(simple).to.be(true); + }); + + it('identifies self-intersecting polygons', function() { + var p4 = replay.createPoint_(2, 5, 4); + var p5 = replay.createPoint_(4, 2, 5); + replay.insertItem_(p0, p4, list, rtree); + replay.insertItem_(p4, p5, list, rtree); + replay.insertItem_(p5, p0, list, rtree); + var simple = replay.isSimple_(list, rtree); + expect(simple).to.be(false); + }); + }); + }); }); From e124b34b24854966480d82b4743983ae030810da Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 10 Oct 2016 17:19:25 +0200 Subject: [PATCH 62/88] Correctly draw polygons with transparent border --- src/ol/render/webgl/linestringreplay/index.js | 2 +- src/ol/render/webgl/replay.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js index 70c694ece6..a9472ba727 100644 --- a/src/ol/render/webgl/linestringreplay/index.js +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -495,6 +495,7 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay = function(gl, context, sk gl.enable(gl.DEPTH_TEST); gl.depthMask(true); gl.depthFunc(gl.NOTEQUAL); + gl.clear(gl.DEPTH_BUFFER_BIT); } if (!ol.obj.isEmpty(skippedFeaturesHash)) { @@ -515,7 +516,6 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay = function(gl, context, sk } } if (!hitDetection) { - gl.clear(gl.DEPTH_BUFFER_BIT); gl.disable(gl.DEPTH_TEST); //Restore GL parameters. gl.depthMask(tmpDepthMask); diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js index 71b228388c..2044856682 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -252,6 +252,13 @@ ol.render.webgl.Replay.prototype.replay = function(context, featureCallback, oneByOne, opt_hitExtent) { var gl = context.getGL(); + if (this.lineStringReplay) { + this.lineStringReplay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } + // bind the vertices buffer ol.DEBUG && console.assert(this.verticesBuffer, 'verticesBuffer must not be null'); @@ -299,13 +306,6 @@ ol.render.webgl.Replay.prototype.replay = function(context, // disable the vertex attrib arrays this.shutDownProgram_(gl, locations); - if (this.lineStringReplay) { - this.lineStringReplay.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent); - } - return result; }; From a00247476117291b564969cbf2a0a18b259b8be5 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 10 Oct 2016 17:24:19 +0200 Subject: [PATCH 63/88] Fix typo in polygon rendering test --- test_rendering/spec/ol/style/polygon.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_rendering/spec/ol/style/polygon.test.js b/test_rendering/spec/ol/style/polygon.test.js index 59748f865b..23843091c0 100644 --- a/test_rendering/spec/ol/style/polygon.test.js +++ b/test_rendering/spec/ol/style/polygon.test.js @@ -142,7 +142,7 @@ describe('ol.rendering.style.Polygon', function() { feature = new ol.Feature({ geometry: new ol.geom.Polygon([ [[-20, -20], [-20, 5], [20, 5], [20, -20], [-20, -20]], - [[-12, -12], [-8, -12], [-8, -3], [-12, -3], [-12, -3]], + [[-12, -3], [-12, -12], [-8, -12], [-8, -3], [-12, -3]], [[0, -12], [13, -12], [13, -3], [0, -3], [0, -12]] ]) From 5fb0a845793a7b3b404e141fbab5c7ec542aa4dd Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 12 Oct 2016 11:00:08 +0200 Subject: [PATCH 64/88] Fix WebGL PolygonReplay#bridgeHole_ --- src/ol/render/webgl/polygonreplay/index.js | 26 +++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js index 9516c13bdd..789114620b 100644 --- a/src/ol/render/webgl/polygonreplay/index.js +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -211,6 +211,7 @@ ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, */ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, list, listMaxX, rtree) { + this.classifyPoints_(hole, rtree, true); var seg = hole.firstItem(); while (seg.p1.x !== holeMaxX) { seg = hole.nextItem(); @@ -227,7 +228,7 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { var currSeg = intersectingSegments[i]; - if (currSeg.p0 !== p1 && currSeg.p1 !== p1) { + if (currSeg.p0.reflex === undefined) { var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, currSeg.p1, true); var dist = Math.abs(p1.x - intersection[0]); @@ -238,17 +239,22 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, } } } + if (minDist === Infinity) { + return; + } bestPoint = seg.p1; - var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree); - if (pointsInTriangle.length) { - var theta = Infinity; - for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) { - var currPoint = pointsInTriangle[i]; - var currTheta = Math.atan2(p1.y - currPoint.y, p2.x - currPoint.x); - if (currTheta < theta || (currTheta === theta && currPoint.x < bestPoint.x)) { - theta = currTheta; - bestPoint = currPoint; + 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; + } } } } From 86c914f3df535085134016750f3210453f28762b Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 12 Oct 2016 20:26:13 +0200 Subject: [PATCH 65/88] Fix glitched lines with rotated view --- .../webgl/linestringreplay/defaultshader.glsl | 12 ++++++---- .../webgl/linestringreplay/defaultshader.js | 24 ++++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/ol/render/webgl/linestringreplay/defaultshader.glsl b/src/ol/render/webgl/linestringreplay/defaultshader.glsl index e18f2f70eb..1658f4e9d7 100644 --- a/src/ol/render/webgl/linestringreplay/defaultshader.glsl +++ b/src/ol/render/webgl/linestringreplay/defaultshader.glsl @@ -16,6 +16,7 @@ attribute float a_direction; uniform mat4 u_projectionMatrix; uniform mat4 u_offsetScaleMatrix; +uniform mat4 u_offsetRotateMatrix; uniform float u_lineWidth; uniform float u_miterLimit; @@ -23,6 +24,7 @@ void main(void) { bool degenerate = false; v_halfWidth = u_lineWidth / 2.0; float miterLimit = u_miterLimit + u_lineWidth; + mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; vec2 offset; v_round = 0.0; float direction = a_direction / abs(a_direction); @@ -65,10 +67,10 @@ void main(void) { shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.); } //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/). - vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.); - vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.); - vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.); - vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.); + vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + offsetMatrix * vec4(longOffset, 0., 0.); + vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0., 0.); + vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0., 0.); + vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0., 0.); float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y); float epsilon = 0.000000000001; float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom; @@ -104,7 +106,7 @@ void main(void) { } } if (!degenerate) { - vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.); + vec4 offsets = offsetMatrix * vec4(offset, 0., 0.); gl_Position = projPos + offsets; } } diff --git a/src/ol/render/webgl/linestringreplay/defaultshader.js b/src/ol/render/webgl/linestringreplay/defaultshader.js index cf48042084..ef95508f13 100644 --- a/src/ol/render/webgl/linestringreplay/defaultshader.js +++ b/src/ol/render/webgl/linestringreplay/defaultshader.js @@ -28,7 +28,7 @@ ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precisio * @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 l;uniform vec4 m;uniform vec2 n;uniform float o;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*n.x*o,(b.y+1.0)/2.0*n.y*o);if(length(windowCoords-gl_FragCoord.xy)>c*o){discard;}} gl_FragColor=m;float alpha=m.a*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; +ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float 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;}'; /** @@ -58,14 +58,14 @@ ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vert * @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 float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n float miterLimit = u_miterLimit + u_lineWidth;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n float miterLimit = u_miterLimit + u_lineWidth;\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + offsetMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = offsetMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\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 float j;uniform float k;void main(void){bool degenerate=false;c=j/2.0;float miterLimit=k+j;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+i*vec4(longOffset,0.,0.);vec4 p2=projPos+i*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+i*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+i*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=i*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;void main(void){bool degenerate=false;c=k/2.0;float miterLimit=l+k;mat4 offsetMatrix=i*j;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+offsetMatrix*vec4(longOffset,0.,0.);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; /** @@ -92,19 +92,25 @@ ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) * @type {WebGLUniformLocation} */ this.u_color = gl.getUniformLocation( - program, ol.DEBUG ? 'u_color' : 'm'); + program, ol.DEBUG ? 'u_color' : 'n'); /** * @type {WebGLUniformLocation} */ this.u_lineWidth = gl.getUniformLocation( - program, ol.DEBUG ? 'u_lineWidth' : 'j'); + program, ol.DEBUG ? 'u_lineWidth' : 'k'); /** * @type {WebGLUniformLocation} */ this.u_miterLimit = gl.getUniformLocation( - program, ol.DEBUG ? 'u_miterLimit' : 'k'); + program, ol.DEBUG ? 'u_miterLimit' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'j'); /** * @type {WebGLUniformLocation} @@ -116,13 +122,13 @@ ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) * @type {WebGLUniformLocation} */ this.u_opacity = gl.getUniformLocation( - program, ol.DEBUG ? 'u_opacity' : 'l'); + program, ol.DEBUG ? 'u_opacity' : 'm'); /** * @type {WebGLUniformLocation} */ this.u_pixelRatio = gl.getUniformLocation( - program, ol.DEBUG ? 'u_pixelRatio' : 'o'); + program, ol.DEBUG ? 'u_pixelRatio' : 'p'); /** * @type {WebGLUniformLocation} @@ -134,7 +140,7 @@ ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) * @type {WebGLUniformLocation} */ this.u_size = gl.getUniformLocation( - program, ol.DEBUG ? 'u_size' : 'n'); + program, ol.DEBUG ? 'u_size' : 'o'); /** * @type {number} From ac6408be3bc2b988df28efebc7245b90df5ec7e3 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Sun, 30 Oct 2016 16:33:04 +0100 Subject: [PATCH 66/88] Add circle replay to WebGL renderer --- src/ol/render/canvas/replaygroup.js | 1 + src/ol/render/replay.js | 1 + src/ol/render/replaytype.js | 1 + .../webgl/circlereplay/defaultshader.glsl | 101 +++++ .../webgl/circlereplay/defaultshader.js | 162 +++++++ src/ol/render/webgl/circlereplay/index.js | 418 ++++++++++++++++++ src/ol/render/webgl/replay.js | 6 +- src/ol/render/webgl/replaygroup.js | 4 +- src/ol/renderer/vector.js | 8 +- 9 files changed, 695 insertions(+), 7 deletions(-) create mode 100644 src/ol/render/webgl/circlereplay/defaultshader.glsl create mode 100644 src/ol/render/webgl/circlereplay/defaultshader.js create mode 100644 src/ol/render/webgl/circlereplay/index.js diff --git a/src/ol/render/canvas/replaygroup.js b/src/ol/render/canvas/replaygroup.js index 51a9646099..40a7722be2 100644 --- a/src/ol/render/canvas/replaygroup.js +++ b/src/ol/render/canvas/replaygroup.js @@ -282,6 +282,7 @@ ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function( * number, boolean)>} */ ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = { + 'Circle': ol.render.canvas.PolygonReplay, 'Image': ol.render.canvas.ImageReplay, 'LineString': ol.render.canvas.LineStringReplay, 'Polygon': ol.render.canvas.PolygonReplay, diff --git a/src/ol/render/replay.js b/src/ol/render/replay.js index 3803f79f0c..a0a8e0dc7e 100644 --- a/src/ol/render/replay.js +++ b/src/ol/render/replay.js @@ -9,6 +9,7 @@ goog.require('ol.render.ReplayType'); */ ol.render.replay.ORDER = [ ol.render.ReplayType.POLYGON, + ol.render.ReplayType.CIRCLE, ol.render.ReplayType.LINE_STRING, ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT diff --git a/src/ol/render/replaytype.js b/src/ol/render/replaytype.js index 28d37e2ae1..5423cee151 100644 --- a/src/ol/render/replaytype.js +++ b/src/ol/render/replaytype.js @@ -5,6 +5,7 @@ goog.provide('ol.render.ReplayType'); * @enum {string} */ ol.render.ReplayType = { + CIRCLE: 'Circle', IMAGE: 'Image', LINE_STRING: 'LineString', POLYGON: 'Polygon', diff --git a/src/ol/render/webgl/circlereplay/defaultshader.glsl b/src/ol/render/webgl/circlereplay/defaultshader.glsl new file mode 100644 index 0000000000..184155e717 --- /dev/null +++ b/src/ol/render/webgl/circlereplay/defaultshader.glsl @@ -0,0 +1,101 @@ +//! NAMESPACE=ol.render.webgl.circlereplay.defaultshader +//! CLASS=ol.render.webgl.circlereplay.defaultshader + + +//! COMMON +varying vec2 v_center; +varying vec2 v_offset; +varying float v_halfWidth; + + +//! VERTEX +attribute vec2 a_position; +attribute float a_instruction; +attribute float a_radius; + +uniform mat4 u_projectionMatrix; +uniform mat4 u_offsetScaleMatrix; +uniform mat4 u_offsetRotateMatrix; +uniform float u_lineWidth; + +void main(void) { + mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; + v_center = vec4(u_projectionMatrix * vec4(a_position, 0., 1.)).xy; + float newX, newY; + float lineWidth = u_lineWidth; + if (lineWidth == 0.0) { + lineWidth = 2.0; + } + v_halfWidth = u_lineWidth / 2.0; + vec2 offset; + // Radius with anitaliasing (roughly). + float radius = a_radius + 3.0; + // Until we get gl_VertexID in WebGL, we store an instruction. + if (a_instruction == 0.0) { + newX = a_position.x - radius; + newY = a_position.y - radius; + // Offsetting the edges of the triangle by lineWidth / 2 is necessary, however + // we should also leave some space for the antialiasing, thus we offset by lineWidth. + offset = vec2(-lineWidth, -lineWidth); + } else { + float sqrtVal = sqrt(2.0) + 1.0; + if (a_instruction == 1.0) { + newX = a_position.x + sqrtVal * radius; + newY = a_position.y - radius; + offset = vec2(lineWidth * sqrtVal, -lineWidth); + } else { + newX = a_position.x - radius; + newY = a_position.y + sqrtVal * radius; + offset = vec2(-lineWidth, lineWidth * sqrtVal); + } + } + + gl_Position = u_projectionMatrix * vec4(newX, newY, 0., 1.) + offsetMatrix * + vec4(offset, 0., 0.); + v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y, 0., 1.)).xy; +} + + +//! FRAGMENT + +uniform float u_opacity; +uniform vec4 u_fillColor; +uniform vec4 u_strokeColor; +uniform vec2 u_size; +uniform float u_pixelRatio; + +void main(void) { + vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * u_pixelRatio, + (v_center.y + 1.0) / 2.0 * u_size.y * u_pixelRatio); + vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * u_pixelRatio, + (v_offset.y + 1.0) / 2.0 * u_size.y * u_pixelRatio); + float radius = length(windowCenter - windowOffset); + float dist = length(windowCenter - gl_FragCoord.xy); + if (dist > (radius + v_halfWidth) * u_pixelRatio) { + if (u_strokeColor.a == 0.0) { + gl_FragColor = u_fillColor; + } else { + gl_FragColor = u_strokeColor; + } + gl_FragColor.a = gl_FragColor.a - (dist - (radius + v_halfWidth) * u_pixelRatio); + } else if (u_fillColor.a == 0.0) { + // Hooray, no fill, just stroke. We can use real antialiasing. + gl_FragColor = u_strokeColor; + if (dist < (radius - v_halfWidth) * u_pixelRatio) { + gl_FragColor.a = gl_FragColor.a - ((radius - v_halfWidth) * u_pixelRatio - dist); + } + } else { + gl_FragColor = u_fillColor; + float strokeDist = (radius - v_halfWidth) * u_pixelRatio; + if (dist > strokeDist) { + gl_FragColor = u_strokeColor; + } else if (dist >= strokeDist - 2.0) { + float step = smoothstep(strokeDist - 2.0, strokeDist, dist); + gl_FragColor = mix(u_fillColor, u_strokeColor, step); + } + } + gl_FragColor.a = gl_FragColor.a * u_opacity; + if (gl_FragColor.a <= 0.0) { + discard; + } +} diff --git a/src/ol/render/webgl/circlereplay/defaultshader.js b/src/ol/render/webgl/circlereplay/defaultshader.js new file mode 100644 index 0000000000..6a79040e7c --- /dev/null +++ b/src/ol/render/webgl/circlereplay/defaultshader.js @@ -0,0 +1,162 @@ +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.circlereplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @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;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_fillColor;\nuniform vec4 u_strokeColor;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_center.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_offset.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n float radius = length(windowCenter - windowOffset);\n float dist = length(windowCenter - gl_FragCoord.xy);\n if (dist > (radius + v_halfWidth) * u_pixelRatio) {\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) * u_pixelRatio);\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) * u_pixelRatio) {\n gl_FragColor.a = gl_FragColor.a - ((radius - v_halfWidth) * u_pixelRatio - dist);\n }\n } else {\n gl_FragColor = u_fillColor;\n float strokeDist = (radius - v_halfWidth) * u_pixelRatio;\n if (dist > strokeDist) {\n gl_FragColor = u_strokeColor;\n } else if (dist >= strokeDist - 2.0) {\n float step = smoothstep(strokeDist - 2.0, 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;uniform float k;uniform vec4 l;uniform vec4 m;uniform vec2 n;uniform float o;void main(void){vec2 windowCenter=vec2((a.x+1.0)/2.0*n.x*o,(a.y+1.0)/2.0*n.y*o);vec2 windowOffset=vec2((b.x+1.0)/2.0*n.x*o,(b.y+1.0)/2.0*n.y*o);float radius=length(windowCenter-windowOffset);float dist=length(windowCenter-gl_FragCoord.xy);if(dist>(radius+c)*o){if(m.a==0.0){gl_FragColor=l;}else{gl_FragColor=m;}gl_FragColor.a=gl_FragColor.a-(dist-(radius+c)*o);}else if(l.a==0.0){gl_FragColor=m;if(dist<(radius-c)*o){gl_FragColor.a=gl_FragColor.a-((radius-c)*o-dist);}} else{gl_FragColor=l;float strokeDist=(radius-c)*o;if(dist>strokeDist){gl_FragColor=m;}else if(dist>=strokeDist-2.0){float step=smoothstep(strokeDist-2.0,strokeDist,dist);gl_FragColor=mix(l,m,step);}} gl_FragColor.a=gl_FragColor.a*k;if(gl_FragColor.a<=0.0){discard;}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + 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;\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;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n v_center = vec4(u_projectionMatrix * vec4(a_position, 0., 1.)).xy;\n float newX, newY;\n float lineWidth = u_lineWidth;\n if (lineWidth == 0.0) {\n lineWidth = 2.0;\n }\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n // Radius with anitaliasing (roughly).\n float radius = a_radius + 3.0;\n // Until we get gl_VertexID in WebGL, we store an instruction.\n if (a_instruction == 0.0) {\n newX = a_position.x - radius;\n newY = a_position.y - radius;\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(-lineWidth, -lineWidth);\n } else {\n float sqrtVal = sqrt(2.0) + 1.0;\n if (a_instruction == 1.0) {\n newX = a_position.x + sqrtVal * radius;\n newY = a_position.y - radius;\n offset = vec2(lineWidth * sqrtVal, -lineWidth);\n } else {\n newX = a_position.x - radius;\n newY = a_position.y + sqrtVal * radius;\n offset = vec2(-lineWidth, lineWidth * sqrtVal);\n }\n }\n\n gl_Position = u_projectionMatrix * vec4(newX, newY, 0., 1.) + offsetMatrix *\n vec4(offset, 0., 0.);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y, 0., 1.)).xy;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying vec2 b;varying float c;attribute vec2 d;attribute float e;attribute float f;uniform mat4 g;uniform mat4 h;uniform mat4 i;uniform float j;void main(void){mat4 offsetMatrix=h*i;a=vec4(g*vec4(d,0.,1.)).xy;float newX,newY;float lineWidth=j;if(lineWidth==0.0){lineWidth=2.0;}c=j/2.0;vec2 offset;float radius=f+3.0;if(e==0.0){newX=d.x-radius;newY=d.y-radius;offset=vec2(-lineWidth,-lineWidth);}else{float sqrtVal=sqrt(2.0)+1.0;if(e==1.0){newX=d.x+sqrtVal*radius;newY=d.y-radius;offset=vec2(lineWidth*sqrtVal,-lineWidth);}else{newX=d.x-radius;newY=d.y+sqrtVal*radius;offset=vec2(-lineWidth,lineWidth*sqrtVal);}} gl_Position=g*vec4(newX,newY,0.,1.)+offsetMatrix*vec4(offset,0.,0.);b=vec4(g*vec4(d.x+f,d.y,0.,1.)).xy;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + 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) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_fillColor = gl.getUniformLocation( + program, ol.DEBUG ? 'u_fillColor' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_lineWidth = gl.getUniformLocation( + program, ol.DEBUG ? 'u_lineWidth' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'h'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'k'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_pixelRatio = gl.getUniformLocation( + program, ol.DEBUG ? 'u_pixelRatio' : 'o'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'g'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_size = gl.getUniformLocation( + program, ol.DEBUG ? 'u_size' : 'n'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_strokeColor = gl.getUniformLocation( + program, ol.DEBUG ? 'u_strokeColor' : 'm'); + + /** + * @type {number} + */ + this.a_instruction = gl.getAttribLocation( + program, ol.DEBUG ? 'a_instruction' : 'e'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'd'); + + /** + * @type {number} + */ + this.a_radius = gl.getAttribLocation( + program, ol.DEBUG ? 'a_radius' : 'f'); +}; diff --git a/src/ol/render/webgl/circlereplay/index.js b/src/ol/render/webgl/circlereplay/index.js new file mode 100644 index 0000000000..f96afe3410 --- /dev/null +++ b/src/ol/render/webgl/circlereplay/index.js @@ -0,0 +1,418 @@ +goog.provide('ol.render.webgl.CircleReplay'); + +goog.require('ol'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.render.webgl.circlereplay.defaultshader'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.CircleReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.circlereplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.|number>>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {number} + */ + this.radius_ = 0; + + /** + * @private + * @type {{fillColor: (Array.|null), + * strokeColor: (Array.|null), + * lineDash: Array., + * lineWidth: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + fillColor: null, + strokeColor: null, + lineDash: null, + lineWidth: undefined, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.CircleReplay, ol.render.webgl.Replay); + + +/** + * @private + * @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.indices[numIndices++] = n++; + this.indices[numIndices++] = n++; + this.indices[numIndices++] = n++; + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.drawCircle = function(circleGeometry, feature) { + var radius = circleGeometry.getRadius(); + var stride = circleGeometry.getStride(); + if (radius) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + + this.radius_ = radius; + var flatCoordinates = circleGeometry.getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, 2, + stride, -this.origin[0], -this.origin[1]); + this.drawCoordinates_(flatCoordinates, 0, 2, stride); + } else { + if (this.state_.changed) { + this.styles_.pop(); + if (this.styles_.length) { + var lastState = this.styles_[this.styles_.length - 1]; + this.state_.fillColor = /** @type {Array.} */ (lastState[0]); + this.state_.strokeColor = /** @type {Array.} */ (lastState[1]); + this.state_.lineWidth = /** @type {number} */ (lastState[2]); + this.state_.changed = false; + } + } + } +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.CircleReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other PolygonReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.circlereplay.defaultshader.fragment; + vertexShader = ol.render.webgl.circlereplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.circlereplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 16, 0); + + gl.enableVertexAttribArray(locations.a_instruction); + gl.vertexAttribPointer(locations.a_instruction, 1, ol.webgl.FLOAT, + false, 16, 8); + + gl.enableVertexAttribArray(locations.a_radius); + gl.vertexAttribPointer(locations.a_radius, 1, ol.webgl.FLOAT, + false, 16, 12); + + // Enable renderer specific uniforms. + gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_pixelRatio, pixelRatio); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_instruction); + gl.disableVertexAttribArray(locations.a_radius); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, /** @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) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, /** @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) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, /** @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); + } + } +}; + + +/** + * @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); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var strokeStyleColor, strokeStyleWidth; + if (strokeStyle) { + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.webgl.defaultLineDash; + strokeStyleColor = strokeStyle.getColor(); + if (!(strokeStyleColor instanceof CanvasGradient) && + !(strokeStyleColor instanceof CanvasPattern)) { + strokeStyleColor = ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultStrokeStyle; + } else { + strokeStyleColor = ol.render.webgl.defaultStrokeStyle; + } + strokeStyleWidth = strokeStyle.getWidth(); + strokeStyleWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.webgl.defaultLineWidth; + } else { + strokeStyleColor = [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/replay.js b/src/ol/render/webgl/replay.js index 2044856682..b82beb53cc 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -132,7 +132,8 @@ ol.render.webgl.Replay.prototype.finish = function(context) {}; * @param {ol.webgl.Context} context Context. * @param {ol.Size} size Size. * @param {number} pixelRatio Pixel ratio. - * @return {ol.render.webgl.imagereplay.defaultshader.Locations| + * @return {ol.render.webgl.circlereplay.defaultshader.Locations| + ol.render.webgl.imagereplay.defaultshader.Locations| ol.render.webgl.linestringreplay.defaultshader.Locations| ol.render.webgl.polygonreplay.defaultshader.Locations} Locations. */ @@ -143,7 +144,8 @@ ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixe * @abstract * @protected * @param {WebGLRenderingContext} gl gl. - * @param {ol.render.webgl.imagereplay.defaultshader.Locations| + * @param {ol.render.webgl.circlereplay.defaultshader.Locations| + ol.render.webgl.imagereplay.defaultshader.Locations| ol.render.webgl.linestringreplay.defaultshader.Locations| ol.render.webgl.polygonreplay.defaultshader.Locations} locations Locations. */ diff --git a/src/ol/render/webgl/replaygroup.js b/src/ol/render/webgl/replaygroup.js index 30ff9761ef..bd2e29a373 100644 --- a/src/ol/render/webgl/replaygroup.js +++ b/src/ol/render/webgl/replaygroup.js @@ -4,6 +4,7 @@ goog.require('ol'); goog.require('ol.array'); goog.require('ol.render.ReplayGroup'); goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.CircleReplay'); goog.require('ol.render.webgl.ImageReplay'); goog.require('ol.render.webgl.LineStringReplay'); goog.require('ol.render.webgl.PolygonReplay'); @@ -104,7 +105,7 @@ ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { var Constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; ol.DEBUG && console.assert(Constructor !== undefined, replayType + - ' constructor missing from ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_'); + ' constructor missing from ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_'); replay = new Constructor(this.tolerance_, this.maxExtent_); replays[replayType] = replay; } @@ -308,6 +309,7 @@ ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1]; * ol.Extent)>} */ 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 diff --git a/src/ol/renderer/vector.js b/src/ol/renderer/vector.js index e2832d049d..ffa74f0889 100644 --- a/src/ol/renderer/vector.js +++ b/src/ol/renderer/vector.js @@ -47,10 +47,10 @@ ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style var fillStyle = style.getFill(); var strokeStyle = style.getStroke(); if (fillStyle || strokeStyle) { - var polygonReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.POLYGON); - polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); - polygonReplay.drawCircle(geometry, feature); + var circleReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.CIRCLE); + circleReplay.setFillStrokeStyle(fillStyle, strokeStyle); + circleReplay.drawCircle(geometry, feature); } var textStyle = style.getText(); if (textStyle) { From 58afee6706e29b21d45357203bf5ba2ccc192f90 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Sun, 30 Oct 2016 16:37:00 +0100 Subject: [PATCH 67/88] Clean up polygon replay GLSL --- src/ol/render/webgl/polygonreplay/defaultshader.glsl | 3 +-- src/ol/render/webgl/polygonreplay/defaultshader.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ol/render/webgl/polygonreplay/defaultshader.glsl b/src/ol/render/webgl/polygonreplay/defaultshader.glsl index 9f5102bcda..489fc52133 100644 --- a/src/ol/render/webgl/polygonreplay/defaultshader.glsl +++ b/src/ol/render/webgl/polygonreplay/defaultshader.glsl @@ -13,8 +13,7 @@ uniform mat4 u_offsetScaleMatrix; uniform mat4 u_offsetRotateMatrix; void main(void) { - vec4 offsets = u_offsetScaleMatrix * vec4(0., 0., 0., 0.); - gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets; + gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.); } diff --git a/src/ol/render/webgl/polygonreplay/defaultshader.js b/src/ol/render/webgl/polygonreplay/defaultshader.js index 7c691999e9..723f17074a 100644 --- a/src/ol/render/webgl/polygonreplay/defaultshader.js +++ b/src/ol/render/webgl/polygonreplay/defaultshader.js @@ -58,14 +58,14 @@ ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Vertex, ol.webgl.Vertex) * @const * @type {string} */ -ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n vec4 offsets = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; +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., 1.);\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){vec4 offsets=c*vec4(0.,0.,0.,0.);gl_Position=b*vec4(a,0.,1.)+offsets;}'; +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.,1.);}'; /** From dd7e49c2171ad43cd82b301c85ed7fd4a45e2290 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Sun, 30 Oct 2016 16:49:07 +0100 Subject: [PATCH 68/88] Fix canvas cricle test --- .../layer/expected/vector-canvas-opaque.png | Bin 2232 -> 2131 bytes .../spec/ol/layer/expected/vector-canvas.png | Bin 2236 -> 3402 bytes test_rendering/spec/ol/layer/vector.test.js | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/test_rendering/spec/ol/layer/expected/vector-canvas-opaque.png b/test_rendering/spec/ol/layer/expected/vector-canvas-opaque.png index 5781ffd7ede52dca51c87e4667cbae9b85fa2008..dcdff4669b4f5f2c9c805f263e135561211565c5 100644 GIT binary patch delta 2118 zcmV-M2)XyT5z`QmB!3!7L_t(|UhSMsTH8nvhQE?LW_BSb7&t+UZPqa-2%G@38Iu*x z3F4d}WEDIc%n2YTz^w5fCkS(bB)bWNy5=caGO{CC-IipVvAu-umg?>9`s->D*1tpq zC{gcqXN&-rnjK#A&?n$1dwcM9=Y9+zgWf5n2KvLSW=q?mYkx=a-r((Sdkmoe$JNB5 zFSYE}H9&n{zqDKfOaM>1t^v*y;Hq0^-`WP58?8WTysFkYXl)Y#Tvh8FTxn~7Gw8iF zz!{)Lt+b_Ifraz_CjHB=`_iw#!e9M@ z#p(?~?E_TsC4XYZ9f-#PF>MmT1w8!m08A~%vTp`|?qm{vE+s%&Y1V@}0dtiITOO<{ zMY$_o5H5J-Q~^{3Dl_gu)KYotQ<^Jt=>Qg+-S+_30sMfXhJw|Wj7IEUn$n=~xxZyW zRbX)gzzcv*d$rPsBxCx0mj+;|+1Y2tMzotefWfCKpF2%+i-o}%s))xrf!;Ht)jKX9%?fd$07Ve{;qe-vZHMfN zu6~V;qJK?9(V!~@h_MQpExdRMr+q-V9EN;fq{7Pq?D-i$Tnx2KIi7l86I1gghvUB4Ym5+KtbZ2^4WtL;{%=|lU$xsQvA zG6y*H`kk);eTWOs+*t=I(*#R{tL}3o>;w3{%G=vfIy-ZK!*7t zBEo<{1Ha6~vaAAu3(P2Z>8qC7u-}EP95aC3I+#zQbYZ3deQOqW>=ySYxiw!MvUkEFluQA7mVa!&cU&H2lBcc0Fu_Lf7&p#_UDfp;r-f%G z0Ofs)XPtp0W5aZKrT|OL+debh^p4AtAdUS^3g#OiV%&6(%d)DBe-fU#F>b&R%mDC| zh+lVWyE~IFkQuIxp#&edZ;Thxi}I8i`M{~lXl88$nv6UtoGH6X6C4mi?o=m&fW>Q7z4!`cni z0lyMGraMj$RFomnS(!5eG>08I5G71JiJ0CcS@M!NUPQMMb*fn7S)VjtDvO~T$}#Z9k7I|S93qbdf@3O$hNSFA@ig$|S%q?=EQaU( z>tq4Kk_lcMD5&KM!VzEnT#1&jDlTc;goB#?tMl}H#msQGi z1rAqX=UxgW(@5#t4~`@l4yAOLnFa(5{F-RDW*U;t=Ym%Xd=XW5V*=1SSgJ>z>C6G3 z-udceG9gOedDT>spmj@kgYDc>U^!G``!uyfrI_>V%mMld&A9VbU4MYH7?R08x+~DH zW}SAPojJfKFmy8Qe09`L!_P|!aner=8&CH@kXS?wsYNK@#v#u`>WIDM&})hPg-+Y0mrEH-bYfboS% z6=+&;;>Pi-XBqgRTYsN3cYuBsD1g3B0b`udY5u>|>|8Qq+oXDS?f?T3N?WQxqv}`E z1QR!w=W2z9eQ|vk0?>zODW~OLFHpIIq}RFP1fAxX+t%YHGm*0Fs!4^ur`JcHHwiu? z@wu>$K;Dg^D}Y{^UN6uYKdy~mcLVU9%IEfV6BSXQ<_^$*pnq4waVXJB+P5jAon&<) zcjf?G=vdpR$ynGHo_rzqvLPVJf)~VSbdSrQ;#SZs|1X_&PMF-J><~p7Rp`dD*2&)dpsnd zBIzv#{+3eRw101dE+xR=w(+j~SG?Qnlh^sjuly5l{`y$}YALeXy5QrsY>4VvoE0{G zSy`F*=h3Zpm6~WkjeT?iu)dEz&G&n5FpE_+eW#^gfu;h@yZstqLgH(HYk&y}B3Hhx7W(F|n wz^l~%mrud3wzV~#`X}qGeb>$O5v$VvKY&0VBf8sUQUCw|07*qoM6N<$f))V)Q~&?~ delta 2220 zcmV;d2vhgd5V#SLB!7QNL_t(|ob8-#XdK5G$2Wz_ts~n>T9X&3o2G≧71#<|GS? zG-+L&FKznrIWG_b^>;Tkjup#pisRbp2T@7;-;?ClL=f~bn3h08<3u&G=9yC@_v)qs zyLzz;1;_njuw}jU!_6t@_3hoP_V(_C?hgxMcjwt>e%hI5=6{*lbDV}Mqg3C-BdLD% zMB}hH`X(N^3ts2o`G*L+NZ{4RVRbBa4xWE#4IqJcnG0Pt|L@lMLEtXKYF9CUyPSqf zswk@lKzlU+MpS5kn>srCqitA#jp*!)wqXHo=IC5B+C~7l>H5Y{Od0_1RRdr|Z8(4g z-XYR@kMQ&>|X0F!Q}lUzPYU?%XAdDfhO z{#ZhNAfetj#|HxXV*<~adzK`W63{1-Zl*I1-|7}%NALJ{0{@D@pA&dlLY*Uz-y(3= z?lV*Omn)y>%Qh3ZOF$nXmp>u!GJ*dopNLrK?^|jAomn*ej;QXtqgs`ZWU25_nd4 z`lnIs)dj#rU$&V%{v&~J6Zp7I6OF1wU$$95pCIrexqO@Vs@a3eN zk?W~M6n~j$eVM=)Q~m0Rb(F6iz?3)9M&SQQs6V%zo_rvz^OaKD>B$EO{2_s_iL~Aw zrfkgs61bJX|0D1SfxjBY={S@a%{CcdZVjBib35%;Qx}#?>Vch_t`odyH;24 zBbVPJ@PZiBEjC4}4siFGsrw0hNkIQ8jwl;diGR^-6S@2mf&Xq3SgH8YO+c(E4IHmpo^UX-qSq0D{N0cfUH^8`L#%Au5}_T`fU zaUUs9?JEf3sis^tfM$9&TdrR*k#Ht%cm$py@JK0N%s+jBz}+K-b6w| z{f5BPl$-wL7k6$GcuGLOEzZ@R3s3KtP=9YyE^oMO=D3fBeU4wo=A{5T@d8Y`nN9*P zr}`$oW#ep6#Y|MZCZUuZP|sS}d5*vt0$)ve6Diy06jO3A^KAmJ$N|+}#7?{bCDd=p z7Cgkpoa*2(_i_peJy!BMjpRY#NT)Un&uQ3#fsd2SAWHl zLz%}2e3d*Nw6)=vgc_#Y5NfSChXp91ddTAoMf{BqpozZA1n#nR)L$_I|3N~j{F@hM zoR}ZynM8pW&_hAbDW{~Hd4|AC{^fvJ0h<1AB~0qPuwp51B0=Eg!ak2I$BE<{=Ox|D z4+y-JkIrEM?j0K2X6o2sQ}>DhG=IyBbGA5f_7QkV_NyoT&#{r& z?6g{BP8}aW^Ux)VGL_4vq*zfI!H*Jn z-e!d?&}J@&INy;Te>mtlWo@{8wCc$O$8j99f#*|eM@yu$B-C(A@5~lkThE^U>(>mt zoBo_&Sb!4hJQZ0GiyvTG@1a~q#%k9>P{opNMpABpXOh6rm9t~|ynoLKyijZzH4MPE zfyu`Syb_R@tx8J&a{+zG#@SL8>qw73Y!al)1lHv8Vd3iM*HZefex7ne6;0qf0(z*h z+1ftmo_k$BOyGY6*ohyYfc_UBq1U^RKDFWZ3FBg?=nK$%UKH5fkj^mha zAeYds-J+6-#y5ngUw{oe zUGUF_Y68%#^3MwB6Q!K4tF0Zmx%_3ShpipvzMl|yvDorpy~)g??g&@^sMLK`1?V_y zGm9&&{nHPItW(uwKHr@saJt;R)dvXPO5iNH{8626u$(T{bbs@zIzY!++v0n*?zZ^$ ztbWC8@twD=OHBZpGOd)`&^=NQZ(?FJ+a#ghr;V`5JTH;fy9s=az#rD~Gl%xyU-*WE z`U`EO&m7jAPha>BfiDvHib(4{VarFAnXAtd_+s0@*BqcJztu$NJLK}oN_+%Z zPUiEncg-_Bn}2{&OmzV;FX^fmD7Pnhp1=pfq^@jblB)*@e3iggq^Euy#a>+jG`6;y z?}D$B*`HhqyCx;oZERjAaChhL&VRiE{?-@3y!H-_Z6l8lQtm^<6#@M>3DuKyGtUI= zSN4}rc@y80P(3CYx017Jio07g`$0XBYoY&`%QKYthT=kFY+@dJ!jO?xx= ukD}4J5hZP-N9WBQZNnZ#qH{y*)c*k)GXU1fK?1@60000|o2nE4iME3>HL0VWsfNNZPN$o8R769WQOXcSkxBv~ zBqZ5v-XYoS^WNJzH`$QKKJMLnHv!4{3v$mn_uS9^&hK~5?|=M$hr(i!MSzmkda*I4 z02bGt<`;R0C%|${?wKcVyF3HnmgUKZSEt2witi@+nue~9G-Fpsj^5t2LUVOEJDQ%n z2}zw!bJoSttfg1RvX=Hm(?>LI_W!r*S1iZhdFIII|?8dA&cCX89j;2#lZ9e?4 z2(7L8=JHPy}^!1L!*aO8*;c6$;0 z{(^#l+SD)y3D7FCG+A?wS ze@|rmv zjt;`74Gvg?r-O8|>C5g%dwA+($ z?ATA`jFw2&L0>X#{zTY$wE(Ns1e7LGD9~}Dx^8yUunhoBS9t-dnexG^8o^HWpnS>P zy!qb&ov188wRa33ee^Rq2BXfGhI)T83=B|d28s$!tKVP2Ec+08Ji=thsG_pp#?#;j`r9y=nC{I;be@)75eLsUb zJ7DDf*rRcvlo=Hz(Q_)1CC;{OpH!dj+o8xZ<^gv{U!LP~REy*)j~R`8B6g;6C{G8B zv^lSASh5 zh6bqBwo8IY0N&qr>dRHTxBqqed&KR9iCgOBm#eEEd?8LOKw;W?EXX&iP-Tg&u8Rb2 z_VYcLXxPVn0z|Qka00OW^S^tRRX+Ti>Hb7<0Qc{=;@r8i>W6*{u>dRG7F<>97IfN% z0!t*Zq8Ui=dr+%u7sa0d+;#MQSbtIt4S4ohTcA;7fW!^|=9|AG4pFfH1J>47$Ss)8 zi$sO!#PVhurPPQh>pTT;pv#44o_Qs3JP;Y+tFJzSD^~FOFBxhcOLf2o1CK=F>IzZB0wm4L&e5<^1E^H@2kmVRK%=n}HGeg)gar8O720=p z9D4Kd>Ai&mC`ey618cH8fF2(BWQfMuaSG{L3K?+>GPE=@bQAtShns`L!@=QV(d*%{ zHlD}bCJx#lp;1x*5-B{`?GLF7=Kv&wdG5J=CI4|&2aQa4jcRM$dirRo7=UEQvCEx{ zf8N51a!G}fK!=aQ`+t2CpsY)@$WKs(oysj&{jhaUc%~mp<3J>HdXWN zc~~B{E)Uwr**OrU03_eJ^Lq+b3?af7fP1@Vr5W(2Y=d+UftN=mLLXTxjPr@ zlQr0z6?ys!%6*m*A=CCPI|Bl5J5g>?lp`H(0pinUc4|6H1z6KXUC{%Mi3OB zPB&0exH}~%z_(i(P%_#*qoZItu$$N5URNGIDBwi9sb&MT4bfCsW?9vmVX72#J4VtBR`IZ)im)#N(C5LSbrYhGRGPR1xOas#TAeR@H88bCp;FM zUMGG{Hyt3cum|ikwr5|{c^-`xZjBBM7ha+{iU!TGFt{ep!--Bj(&@odW=^oqQUQ`a zO1@FyZEZVBn3?fM*Db)K!!+7`G#<~3pOSV0(5KSCz<+wfPG%3IhL6V-KvMag)^S+W z9JVHLHwM6Ze+q0!5p6(b>%g~q7!)L^u{Bep2tZ<$d!0T!mJuDmP=+LC5=bm?lgfm; zeDRup`V_A2WUwVegAFOyB0}Uy;Hd#0K6QApFPn`@0xMd8V*k*b1$ArzYEn?QLYxJj zErA5!E`N&-7X7t@aybCooh&|RcSjHKB8_72+#SgxK#>!qVnF~dPT{#x`eN6C#E3Ld zDX3p1ZWl6p3cur^k)c-$Tem=wGlhu}aX5W=JW3-{ex5=ZKX*40vnech9Y~fj9X=Y5 z-K@(Pxd1~gq&;dKw0!um45$*cPA<1(y#CKqanocrev4CkgfUdYmdO~ zP7eGQkq(s__!QwtGg1+Ks5DBU-2?RcW_qX7)jTX(V1rS)LnU&wS*GNnk^pe=SaV7i zqC3`%;lcWFVk%62g(B)hi2N9%2E4unJbzdx3D*)sJBTi{iU{p+m@*>GbVZ(L3^^i( zcG!Bkkh*V5eFdVkNNkMg&<+AHir^v=kt*ekL=jR^bTU7|MT_BW!cQ|JyzQlz_6340 zBS&YE)=>mlR(LEJQ|p8&4DP=6CJf;gUqLYC~H-{jK#q|8g)ICyi_l<`BJrJ<#KtMN|OMU zNH(nTW?+m`331c}WGo5+N8+CiydQh7r({Fn(g_99UCCiooi7zelJ2?$Sg)6_ABmnj zJ~EMDILZPQ>i)2C&k=vPSfsm7-P)m0!gamR1e-qzJ-izES{B6#6v~nzI)BoO=(mQ! ziG=GG4}(l0;d&V5>LJltNFhPSEa5tE?TNxE|4p18HeUiR@Yme>1gen)txiw*=ODle zl$DelR`pdB(@shGZ($`c<|+Syx5wzxiDq`ZMp$nD=W>0)HPwbYf%URyp@5#F|am{ulJ(oxsQzuYQ^!LGDluc7r9^ ziG;m7i!^q*G&p{I6Z-q3y2^9jr7$(M6L;M41>)kuhQlg$Ysb8Q;C1vu+1KA)9`dGi zkGWIDeiP#y(+DI{RxEFQJw7`_F9r@y7-D z_S>S#Ymlc7?-hB2PB$oAgM90)XK>FwvAfE7-llLQDko3o;OyCaw6@-cp`n$Lst0{; zGF?Dk-q%>WwuK}@M?uv`6pcdaz$p5a%~#` delta 2225 zcmV;i2u}CP8oUvZBYy~kNklw^!^ zK=NMcMeQfM*4nk!diOWqjDO?bNc{fwpF@mAt{3mZ-#!62<$q7FEFKl+2BEdhKYybZ zR_Wc>KW7gnU%`rYU;mt?7FOxC%|Cw|)N?N$73S^$9(?x%;NnqX4uGp~T3h=Sv$I>6 zm^g>a1xGb8agN#9Ev&8m+SfGzuE7F~$EV@(1mSiYjzjAtx7&cn6GS{d{d@ryi!br^ z?e7DF)=6*Q{(p{Q@#XUY*Z?=k9Y?Yw9Rz6#;LK+U|72w@YEQy|X@Fa)i^JoR?(ZZn zr_v`>Ko(aw@EmkP1!OL#(%sf{T+;nBpC#=6Re8e1G{EDM?gy~cRu>l^;q|ImTEerE z*Q?^<;$z2k0ZaV{m`?k#ynH=$7*@5se2vr7&EWuyMt_4t2jG;V(coYKZf&KUUPS%< z2Po@4DC<6meg*=~Hc9j|4}Y(}|KL2tt*z8x06MtyH19eDngtUP;S-bx1-Lv|F4v?0 zmv8zXCL%)gGtj*2ecOr713W$5bYiCoGz&VGdw|791!#w1fMtoK9frY0Qx8z-SPlZs zIT$sa7yZ|auf+c~O~HvOPuIZ#O%nWKblXq}WfN`Okrpkq0dHvQHidc7(PW5qFmCsaVT z`Y@4T6h1)#9~DsFKj<60hU)tV;G+V3f`UjeVt*at)>g_ffa2^3RD`uh){5+$O~vr;mRbU_8u>AtFzlY0ULd;i&&D0R)sPXNx&Zh!X% z*wXaw29T#&?8FO1v4;fUjU=HWD=3x?AOtL~?)=6XzWP9!$Oh3BdaM52g>!tpL?C6$P#O zu=8%zGOC(Rq#L#TuoDYf73!I48DQgJ-7Xuq1?Y0YHb7twqTGk<}SC#>GN+f*)$kQxXE@v5F z{;~#L)f@w;s~Yl`HOl~5F4y)zJ<JiV2EiF*rppM6Px=Q!VX zZ5;u2E9oyD73MHGnSUh($kQxnhnq@eS%IuxJ!vY*>J`ckH*hjOWcyBh`InIxk+0ZGw zD}Qfyr5(1rihoB5(D!!NUY}<@)epO?(}ruFX0da8XO?ETJh-nZ^M>oMDKPhj>z(5~ z_>nhUqd%7ajGsZE#q!UzvHUYFmj6IRMCRF9{xO(KCo+BpJk2&K^M7;cL?3qI&^7RD zPZ}Cuykip}##cYt2M8>|FjnyCQ+DW3NcHJc7KX9Xb$?Ax0{kb-_Vss}i`w+=UzIsh zvGM#b@x|3(=GB{;_`?iFL77)?YVpO@AREvBVrDJv;F$(!S;gYZMp1UCnxEfzURQv& zYJ3T;gSxv0c^eZGisR5GCKSxhZsU(X`n!gq0(`8!Jr5QZRxmZiTZT3@#baS%rO~e) zlsW<&%PPiI@GstlzkTuU;JdL%)B Date: Mon, 31 Oct 2016 20:52:06 +0100 Subject: [PATCH 69/88] Fix some rendering issues with complex styling --- src/ol/render/webgl/linestringreplay/index.js | 5 ++- src/ol/render/webgl/polygonreplay/index.js | 17 ---------- src/ol/render/webgl/replay.js | 32 +++++++++++++++++++ src/ol/renderer/webgl/vectorlayer.js | 2 +- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js index a9472ba727..0dee4eb2c3 100644 --- a/src/ol/render/webgl/linestringreplay/index.js +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -495,7 +495,6 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay = function(gl, context, sk gl.enable(gl.DEPTH_TEST); gl.depthMask(true); gl.depthFunc(gl.NOTEQUAL); - gl.clear(gl.DEPTH_BUFFER_BIT); } if (!ol.obj.isEmpty(skippedFeaturesHash)) { @@ -512,11 +511,13 @@ ol.render.webgl.LineStringReplay.prototype.drawReplay = function(gl, context, sk nextStyle = this.styles_[i]; this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); end = start; } } if (!hitDetection) { gl.disable(gl.DEPTH_TEST); + gl.clear(gl.DEPTH_BUFFER_BIT); //Restore GL parameters. gl.depthMask(tmpDepthMask); gl.depthFunc(tmpDepthFunc); @@ -551,6 +552,7 @@ ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, co if (skippedFeaturesHash[featureUid]) { if (start !== end) { this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); } end = featureStart; } @@ -559,6 +561,7 @@ ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, co } if (start !== end) { this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); } } }; diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js index 789114620b..c805070bf0 100644 --- a/src/ol/render/webgl/polygonreplay/index.js +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -868,16 +868,6 @@ ol.render.webgl.PolygonReplay.prototype.shutDownProgram_ = function(gl, location * @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 { @@ -895,13 +885,6 @@ ol.render.webgl.PolygonReplay.prototype.drawReplay = function(gl, context, skipp end = start; } } - if (!hitDetection) { - gl.clear(gl.DEPTH_BUFFER_BIT); - gl.disable(gl.DEPTH_TEST); - //Restore GL parameters. - gl.depthMask(tmpDepthMask); - gl.depthFunc(tmpDepthFunc); - } }; diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js index b82beb53cc..cde240a930 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -253,12 +253,32 @@ ol.render.webgl.Replay.prototype.replay = function(context, 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); } // bind the vertices buffer @@ -308,6 +328,18 @@ ol.render.webgl.Replay.prototype.replay = function(context, // 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; }; diff --git a/src/ol/renderer/webgl/vectorlayer.js b/src/ol/renderer/webgl/vectorlayer.js index c373c721e4..035cb76ad1 100644 --- a/src/ol/renderer/webgl/vectorlayer.js +++ b/src/ol/renderer/webgl/vectorlayer.js @@ -299,7 +299,7 @@ ol.renderer.webgl.VectorLayer.prototype.renderFeature = function(feature, resolu } var loading = false; if (Array.isArray(styles)) { - for (var i = 0, ii = styles.length; i < ii; ++i) { + 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), From 763bbca549df3d1521b6a88afe65a32471fbf959 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 1 Nov 2016 16:28:56 +0100 Subject: [PATCH 70/88] Fix skipping replay --- src/ol/render/webgl/circlereplay/index.js | 1 + src/ol/render/webgl/linestringreplay/index.js | 1 + src/ol/render/webgl/polygonreplay/index.js | 1 + 3 files changed, 3 insertions(+) diff --git a/src/ol/render/webgl/circlereplay/index.js b/src/ol/render/webgl/circlereplay/index.js index f96afe3410..9a0c7a93b8 100644 --- a/src/ol/render/webgl/circlereplay/index.js +++ b/src/ol/render/webgl/circlereplay/index.js @@ -345,6 +345,7 @@ ol.render.webgl.CircleReplay.prototype.drawReplaySkipping_ = function(gl, contex if (start !== end) { this.drawElements(gl, context, start, end); } + start = end = groupStart; } }; diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js index 0dee4eb2c3..7725243405 100644 --- a/src/ol/render/webgl/linestringreplay/index.js +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -563,6 +563,7 @@ ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, co this.drawElements(gl, context, start, end); gl.clear(gl.DEPTH_BUFFER_BIT); } + start = end = groupStart; } }; diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js index c805070bf0..7d18de928d 100644 --- a/src/ol/render/webgl/polygonreplay/index.js +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -971,6 +971,7 @@ ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, conte if (start !== end) { this.drawElements(gl, context, start, end); } + start = end = groupStart; } }; From dc360c2d7fccf44e6d2531872dbaf79091df26ea Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 2 Nov 2016 09:53:53 +0100 Subject: [PATCH 71/88] Refactor WebGL render tests --- test/spec/ol/render/webgl/imagereplay.test.js | 169 +++++ test/spec/ol/render/webgl/index.test.js | 1 + .../ol/render/webgl/linestringreplay.test.js | 209 ++++++ .../ol/render/webgl/polygonreplay.test.js | 318 ++++++++ test/spec/ol/render/webgl/replay.test.js | 693 ------------------ 5 files changed, 697 insertions(+), 693 deletions(-) create mode 100644 test/spec/ol/render/webgl/imagereplay.test.js create mode 100644 test/spec/ol/render/webgl/index.test.js create mode 100644 test/spec/ol/render/webgl/linestringreplay.test.js create mode 100644 test/spec/ol/render/webgl/polygonreplay.test.js delete mode 100644 test/spec/ol/render/webgl/replay.test.js diff --git a/test/spec/ol/render/webgl/imagereplay.test.js b/test/spec/ol/render/webgl/imagereplay.test.js new file mode 100644 index 0000000000..8ea1997917 --- /dev/null +++ b/test/spec/ol/render/webgl/imagereplay.test.js @@ -0,0 +1,169 @@ +goog.provide('ol.test.render.webgl.ImageReplay'); + +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.Point'); +goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.style.Image'); + +describe('ol.render.webgl.ImageReplay', function() { + var replay; + + var createImageStyle = function(image) { + var imageStyle = new ol.style.Image({ + opacity: 0.1, + rotateWithView: true, + rotation: 1.5, + scale: 2.0 + }); + imageStyle.getAnchor = function() { + return [0.5, 1]; + }; + imageStyle.getImage = function() { + return image; + }; + imageStyle.getHitDetectionImage = function() { + return image; + }; + imageStyle.getImageSize = function() { + return [512, 512]; + }; + imageStyle.getHitDetectionImageSize = function() { + return [512, 512]; + }; + imageStyle.getOrigin = function() { + return [200, 200]; + }; + imageStyle.getSize = function() { + return [256, 256]; + }; + return imageStyle; + }; + + beforeEach(function() { + var tolerance = 0.1; + var maxExtent = [-10000, -20000, 10000, 20000]; + replay = new ol.render.webgl.ImageReplay(tolerance, maxExtent); + }); + + describe('#setImageStyle', function() { + + var imageStyle1, imageStyle2; + + beforeEach(function() { + imageStyle1 = createImageStyle(new Image()); + imageStyle2 = createImageStyle(new Image()); + }); + + it('set expected states', function() { + replay.setImageStyle(imageStyle1); + expect(replay.anchorX_).to.be(0.5); + expect(replay.anchorY_).to.be(1); + expect(replay.height_).to.be(256); + expect(replay.imageHeight_).to.be(512); + expect(replay.imageWidth_).to.be(512); + expect(replay.opacity_).to.be(0.1); + expect(replay.originX_).to.be(200); + expect(replay.originY_).to.be(200); + expect(replay.rotation_).to.be(1.5); + expect(replay.rotateWithView_).to.be(true); + expect(replay.scale_).to.be(2.0); + expect(replay.width_).to.be(256); + expect(replay.images_).to.have.length(1); + expect(replay.groupIndices_).to.have.length(0); + expect(replay.hitDetectionImages_).to.have.length(1); + expect(replay.hitDetectionGroupIndices_).to.have.length(0); + + replay.setImageStyle(imageStyle1); + expect(replay.images_).to.have.length(1); + expect(replay.groupIndices_).to.have.length(0); + expect(replay.hitDetectionImages_).to.have.length(1); + expect(replay.hitDetectionGroupIndices_).to.have.length(0); + + replay.setImageStyle(imageStyle2); + expect(replay.images_).to.have.length(2); + expect(replay.groupIndices_).to.have.length(1); + expect(replay.hitDetectionImages_).to.have.length(2); + expect(replay.hitDetectionGroupIndices_).to.have.length(1); + }); + }); + + describe('#drawPoint', function() { + beforeEach(function() { + var imageStyle = createImageStyle(new Image()); + replay.setImageStyle(imageStyle); + }); + + it('sets the buffer data', function() { + var point; + + point = new ol.geom.Point([1000, 2000]); + replay.drawPoint(point, null); + expect(replay.vertices).to.have.length(32); + expect(replay.indices).to.have.length(6); + expect(replay.indices[0]).to.be(0); + expect(replay.indices[1]).to.be(1); + expect(replay.indices[2]).to.be(2); + expect(replay.indices[3]).to.be(0); + expect(replay.indices[4]).to.be(2); + expect(replay.indices[5]).to.be(3); + + point = new ol.geom.Point([2000, 3000]); + replay.drawPoint(point, null); + expect(replay.vertices).to.have.length(64); + expect(replay.indices).to.have.length(12); + expect(replay.indices[6]).to.be(4); + expect(replay.indices[7]).to.be(5); + expect(replay.indices[8]).to.be(6); + expect(replay.indices[9]).to.be(4); + expect(replay.indices[10]).to.be(6); + expect(replay.indices[11]).to.be(7); + }); + }); + + describe('#drawMultiPoint', function() { + beforeEach(function() { + var imageStyle = createImageStyle(new Image()); + replay.setImageStyle(imageStyle); + }); + + it('sets the buffer data', function() { + var multiPoint; + + multiPoint = new ol.geom.MultiPoint( + [[1000, 2000], [2000, 3000]]); + replay.drawMultiPoint(multiPoint, null); + expect(replay.vertices).to.have.length(64); + expect(replay.indices).to.have.length(12); + expect(replay.indices[0]).to.be(0); + expect(replay.indices[1]).to.be(1); + expect(replay.indices[2]).to.be(2); + expect(replay.indices[3]).to.be(0); + expect(replay.indices[4]).to.be(2); + expect(replay.indices[5]).to.be(3); + expect(replay.indices[6]).to.be(4); + expect(replay.indices[7]).to.be(5); + expect(replay.indices[8]).to.be(6); + expect(replay.indices[9]).to.be(4); + expect(replay.indices[10]).to.be(6); + expect(replay.indices[11]).to.be(7); + + multiPoint = new ol.geom.MultiPoint( + [[3000, 4000], [4000, 5000]]); + replay.drawMultiPoint(multiPoint, null); + expect(replay.vertices).to.have.length(128); + expect(replay.indices).to.have.length(24); + expect(replay.indices[12]).to.be(8); + expect(replay.indices[13]).to.be(9); + expect(replay.indices[14]).to.be(10); + expect(replay.indices[15]).to.be(8); + expect(replay.indices[16]).to.be(10); + expect(replay.indices[17]).to.be(11); + expect(replay.indices[18]).to.be(12); + expect(replay.indices[19]).to.be(13); + expect(replay.indices[20]).to.be(14); + expect(replay.indices[21]).to.be(12); + expect(replay.indices[22]).to.be(14); + expect(replay.indices[23]).to.be(15); + }); + }); +}); diff --git a/test/spec/ol/render/webgl/index.test.js b/test/spec/ol/render/webgl/index.test.js new file mode 100644 index 0000000000..0e257a936e --- /dev/null +++ b/test/spec/ol/render/webgl/index.test.js @@ -0,0 +1 @@ +goog.provide('ol.test.render.webgl.Replay'); diff --git a/test/spec/ol/render/webgl/linestringreplay.test.js b/test/spec/ol/render/webgl/linestringreplay.test.js new file mode 100644 index 0000000000..8616694960 --- /dev/null +++ b/test/spec/ol/render/webgl/linestringreplay.test.js @@ -0,0 +1,209 @@ +goog.provide('ol.test.render.webgl.LineStringReplay'); + +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.style.Stroke'); + +describe('ol.render.webgl.LineStringReplay', function() { + var replay; + + var strokeStyle1 = new ol.style.Stroke({ + color: [0, 255, 0, 0.4] + }); + + var strokeStyle2 = new ol.style.Stroke({ + color: [255, 0, 0, 1], + lineCap: 'square', + lineJoin: 'miter' + }); + + beforeEach(function() { + var tolerance = 0.1; + var maxExtent = [-10000, -20000, 10000, 20000]; + replay = new ol.render.webgl.LineStringReplay(tolerance, maxExtent); + }); + + describe('#setFillStrokeStyle', function() { + + it('set expected states', function() { + replay.setFillStrokeStyle(null, strokeStyle1); + expect(replay.state_).not.be(null); + expect(replay.state_.lineCap).to.be('round'); + expect(replay.state_.lineJoin).to.be('round'); + expect(replay.state_.strokeColor).to.eql([0, 1, 0, 0.4]); + expect(replay.state_.lineWidth).to.be(1); + expect(replay.state_.miterLimit).to.be(10); + expect(replay.state_.changed).to.be(true); + expect(replay.styles_).to.have.length(1); + + replay.setFillStrokeStyle(null, strokeStyle2); + expect(replay.state_.lineCap).to.be('square'); + expect(replay.state_.lineJoin).to.be('miter'); + expect(replay.state_.strokeColor).to.eql([1, 0, 0, 1]); + expect(replay.state_.lineWidth).to.be(1); + expect(replay.state_.miterLimit).to.be(10); + expect(replay.state_.changed).to.be(true); + expect(replay.styles_).to.have.length(2); + + }); + }); + + describe('#drawLineString', function() { + + it('sets the buffer data', function() { + var linestring; + + linestring = new ol.geom.LineString( + [[1000, 2000], [2000, 3000]]); + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(linestring, null); + expect(replay.vertices).to.have.length(56); + expect(replay.indices).to.have.length(18); + expect(replay.state_.changed).to.be(false); + expect(replay.startIndices).to.have.length(1); + expect(replay.startIndicesFeature).to.have.length(1); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000]]); + replay.drawLineString(linestring, null); + expect(replay.vertices).to.have.length(140); + expect(replay.indices).to.have.length(48); + expect(replay.state_.changed).to.be(false); + expect(replay.startIndices).to.have.length(2); + expect(replay.startIndicesFeature).to.have.length(2); + }); + }); + + describe('#drawMultiLineString', function() { + + it('sets the buffer data', function() { + var multilinestring; + + multilinestring = new ol.geom.MultiLineString( + [[[1000, 2000], [2000, 3000]], + [[1000, 3000], [2000, 4000], [3000, 3000]]]); + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawMultiLineString(multilinestring, null); + expect(replay.vertices).to.have.length(140); + expect(replay.indices).to.have.length(48); + expect(replay.state_.changed).to.be(false); + expect(replay.startIndices).to.have.length(1); + expect(replay.startIndicesFeature).to.have.length(1); + }); + }); + + describe('#drawCoordinates_', function() { + + it('triangulates linestrings', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1], + lineCap: 'butt', + lineJoin: 'bevel' + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices).to.eql( + [2, 0, 1, 4, 2, 1, + 2, 4, 3, + 5, 3, 4, 4, 6, 5]); + }); + + it('optionally creates miters', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1], + lineCap: 'butt' + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices).to.eql( + [2, 0, 1, 4, 2, 1, + 2, 4, 3, 3, 5, 2, + 6, 3, 4, 4, 7, 6]); + }); + + it('optionally creates caps', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1] + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices).to.eql( + [2, 0, 1, 1, 3, 2, + 4, 2, 3, 6, 4, 3, + 4, 6, 5, 5, 7, 4, + 8, 5, 6, 6, 9, 8, + 10, 8, 9, 9, 11, 10]); + }); + + it('respects segment orientation', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1], + lineCap: 'butt', + lineJoin: 'bevel' + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 2000], [3000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices).to.eql( + [2, 0, 1, 4, 2, 0, + 2, 4, 3, + 5, 3, 4, 4, 6, 5]); + }); + + it('closes boundaries', function() { + var linestring; + + var stroke = new ol.style.Stroke({ + color: [0, 255, 0, 1], + lineCap: 'butt', + lineJoin: 'bevel' + }); + + linestring = new ol.geom.LineString( + [[1000, 3000], [2000, 4000], [3000, 3000], [1000, 3000]]); + var flatCoordinates = linestring.getFlatCoordinates(); + replay.setFillStrokeStyle(null, stroke); + replay.drawCoordinates_(flatCoordinates, 0, + flatCoordinates.length, 2); + expect(replay.indices).to.eql( + [0, 2, 1, 3, 1, 2, + 5, 3, 2, + 3, 5, 4, 6, 4, 5, + 8, 6, 5, + 6, 8, 7, 9, 7, 8, + 10, 9, 8]); + expect(replay.vertices.slice(0, 7)).to.eql( + replay.vertices.slice(-14, -7)); + expect(replay.vertices.slice(14, 21)).to.eql( + replay.vertices.slice(-7)); + }); + }); +}); diff --git a/test/spec/ol/render/webgl/polygonreplay.test.js b/test/spec/ol/render/webgl/polygonreplay.test.js new file mode 100644 index 0000000000..449a56ab3f --- /dev/null +++ b/test/spec/ol/render/webgl/polygonreplay.test.js @@ -0,0 +1,318 @@ +goog.provide('ol.test.render.webgl.PolygonReplay'); + +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Polygon'); +goog.require('ol.render.webgl.PolygonReplay'); +goog.require('ol.structs.LinkedList'); +goog.require('ol.structs.RBush'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); + +describe('ol.render.webgl.PolygonReplay', function() { + var replay; + + var fillStyle = new ol.style.Fill({ + color: [0, 0, 255, 0.5] + }); + var strokeStyle = new ol.style.Stroke({ + color: [0, 255, 0, 0.4] + }); + + beforeEach(function() { + var tolerance = 0.1; + var maxExtent = [-10000, -20000, 10000, 20000]; + replay = new ol.render.webgl.PolygonReplay(tolerance, maxExtent); + }); + + describe('#drawPolygon', function() { + beforeEach(function() { + replay.setFillStrokeStyle(fillStyle, strokeStyle); + }); + + it('sets the buffer data', function() { + var polygon1 = new ol.geom.Polygon( + [[[1000, 2000], [1200, 2000], [1200, 3000]]] + ); + replay.drawPolygon(polygon1, null); + expect(replay.vertices).to.have.length(6); + expect(replay.indices).to.have.length(3); + + expect(replay.vertices).to.eql([ + 1200, 3000, 1200, 2000, 1000, 2000]); + expect(replay.indices).to.eql([2, 0, 1]); + + var polygon2 = new ol.geom.Polygon( + [[[4000, 2000], [4200, 2000], [4200, 3000]]] + ); + replay.drawPolygon(polygon2, null); + expect(replay.vertices).to.have.length(12); + expect(replay.indices).to.have.length(6); + + expect(replay.vertices).to.eql([ + 1200, 3000, 1200, 2000, 1000, 2000, + 4200, 3000, 4200, 2000, 4000, 2000 + ]); + expect(replay.indices).to.eql([2, 0, 1, 5, 3, 4]); + }); + }); + + describe('#drawMultiPolygon', function() { + beforeEach(function() { + replay.setFillStrokeStyle(fillStyle, strokeStyle); + }); + + it('sets the buffer data', function() { + var multiPolygon = new ol.geom.MultiPolygon([ + [[[1000, 2000], [1200, 2000], [1200, 3000]]], + [[[4000, 2000], [4200, 2000], [4200, 3000]]] + ]); + replay.drawMultiPolygon(multiPolygon, null); + expect(replay.vertices).to.have.length(12); + expect(replay.indices).to.have.length(6); + + expect(replay.vertices).to.eql([ + 1200, 3000, 1200, 2000, 1000, 2000, + 4200, 3000, 4200, 2000, 4000, 2000 + ]); + expect(replay.indices).to.eql([2, 0, 1, 5, 3, 4]); + }); + }); + + describe('triangulating functions', function() { + var list, rtree; + beforeEach(function() { + list = new ol.structs.LinkedList(); + rtree = new ol.structs.RBush(); + }); + + describe('#createPoint_', function() { + it('creates a WebGL polygon vertex', function() { + var p = replay.createPoint_(1, 1, 1); + expect(p.x).to.be(1); + expect(p.y).to.be(1); + expect(p.i).to.be(1); + expect(p.reflex).to.be(undefined); + }); + + it('adds the point to the vertex array', function() { + replay.createPoint_(1, 1, 1); + expect(replay.vertices.length).to.be(2); + expect(replay.vertices[0]).to.be(1); + expect(replay.vertices[1]).to.be(1); + }); + }); + + describe('#insertItem_', function() { + var p0, p1; + beforeEach(function() { + p0 = replay.createPoint_(1, 1, 1); + p1 = replay.createPoint_(2, 2, 2); + }); + + it('creates a WebGL polygon segment', function() { + var seg = replay.insertItem_(p0, p1, list, rtree); + expect(seg.p0).to.be(p0); + expect(seg.p1).to.be(p1); + }); + + it('inserts the segment into the provided linked list', function() { + var seg = replay.insertItem_(p0, p1, list, rtree); + expect(list.head_.data).to.be(seg); + }); + + it('inserts the segment into the R-Tree, if provided', function() { + replay.insertItem_(p0, p1, list); + expect(rtree.isEmpty()).to.be(true); + replay.insertItem_(p0, p1, list, rtree); + expect(rtree.isEmpty()).to.be(false); + }); + }); + + describe('#removeItem_', function() { + var s0, s1; + beforeEach(function() { + var p = replay.createPoint_(2, 2, 2); + s0 = replay.insertItem_(replay.createPoint_(1, 1, 1), + p, list, rtree); + s1 = replay.insertItem_(p, + replay.createPoint_(5, 2, 3), list, rtree); + }); + + it('removes the current item', function() { + replay.removeItem_(s0, s1, list, rtree); + expect(list.head_.data).not.to.be(s1); + expect(rtree.getAll().length).to.be(1); + }); + + it('updates the preceding segment', function() { + var dataExtent = rtree.getExtent(); + replay.removeItem_(s0, s1, list, rtree); + expect(s0.p1).to.be(s1.p1); + expect(rtree.getExtent()).to.eql(dataExtent); + }); + }); + + describe('#getPointsInTriangle_', function() { + var p0, p1, p2, p3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + replay.insertItem_(p0, p1, list, rtree); + replay.insertItem_(p1, p2, list, rtree); + replay.insertItem_(p2, p3, list, rtree); + replay.insertItem_(p3, p0, list, rtree); + replay.classifyPoints_(list, rtree, false); + }); + + it('gets every point in a triangle', function() { + var points = replay.getPointsInTriangle_({x: -3, y: 6}, {x: 7, y: 6}, + {x: 2, y: 2}, rtree); + expect(points).to.eql([p1, p2, p3]); + }); + + it('gets only reflex points in a triangle', function() { + var points = replay.getPointsInTriangle_({x: -3, y: 6}, {x: 7, y: 6}, + {x: 2, y: 2}, rtree, true); + expect(points).to.eql([p2]); + }); + }); + + describe('#getIntersections_', function() { + var p0, p1, p2, p3, s0, s1, s2, s3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + s0 = replay.insertItem_(p0, p1, list, rtree); + s1 = replay.insertItem_(p1, p2, list, rtree); + s2 = replay.insertItem_(p2, p3, list, rtree); + s3 = replay.insertItem_(p3, p0, list, rtree); + }); + + it('gets intersecting, but non touching segments', function() { + var segments = replay.getIntersections_({p0: {x: 0, y: 3}, p1: {x: 4, y: 5}}, + rtree); + expect(segments).to.eql([s0, s1]); + }); + + it('gets intersecting and touching segments', function() { + var segments = replay.getIntersections_({p0: {x: 0, y: 3}, p1: {x: 4, y: 5}}, + rtree, true); + expect(segments).to.eql([s0, s1, s2, s3]); + }); + }); + + describe('#calculateIntersection_', function() { + var p0 = {x: 0, y: 0}; + var p1 = {x: 4, y: 4}; + var p2 = {x: 0, y: 4}; + var p3 = {x: 4, y: 0}; + + it('calculates the intersection point of two intersecting segments', function() { + var i = replay.calculateIntersection_(p0, p1, p2, p3); + var t = replay.calculateIntersection_(p0, p1, p1, p2); + expect(i).to.eql([2, 2]); + expect(t).to.be(undefined); + }); + + it('calculates the intersection point of two touching segments', function() { + var t = replay.calculateIntersection_(p0, p1, p1, p2, true); + expect(t).to.eql([4, 4]); + }); + }); + + describe('#diagonalIsInside_', function() { + var p0, p1, p2, p3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + replay.insertItem_(p0, p1, list, rtree); + replay.insertItem_(p1, p2, list, rtree); + replay.insertItem_(p2, p3, list, rtree); + replay.insertItem_(p3, p0, list, rtree); + replay.classifyPoints_(list, rtree, false); + }); + + it('identifies if diagonal is inside the polygon', function() { + var inside = replay.diagonalIsInside_(p1, p2, p3, p0, p1); + expect(inside).to.be(true); + }); + + it('identifies if diagonal is outside the polygon', function() { + var inside = replay.diagonalIsInside_(p0, p1, p2, p3, p0); + expect(inside).to.be(false); + }); + }); + + describe('#classifyPoints_', function() { + var p0, p1, p2, p3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + replay.insertItem_(p0, p1, list, rtree); + replay.insertItem_(p1, p2, list, rtree); + replay.insertItem_(p2, p3, list, rtree); + replay.insertItem_(p3, p0, list, rtree); + }); + + it('classifies the points of clockwise polygons', function() { + replay.classifyPoints_(list, rtree, false); + expect(p0.reflex).to.be(false); + expect(p1.reflex).to.be(false); + expect(p2.reflex).to.be(true); + expect(p3.reflex).to.be(false); + }); + + it('classifies the points of counter-clockwise polygons', function() { + replay.classifyPoints_(list, rtree, true); + expect(p0.reflex).to.be(true); + expect(p1.reflex).to.be(true); + expect(p2.reflex).to.be(false); + expect(p3.reflex).to.be(true); + }); + + it('removes collinear points', function() { + replay.insertItem_(p3, p0, list, rtree); + replay.classifyPoints_(list, rtree, false); + expect(list.getLength()).to.be(4); + expect(rtree.getAll().length).to.be(4); + }); + }); + + describe('#isSimple_', function() { + var p0, p1, p2, p3; + beforeEach(function() { + p0 = replay.createPoint_(2, 0, 0); + p1 = replay.createPoint_(0, 5, 1); + p2 = replay.createPoint_(2, 3, 2); + p3 = replay.createPoint_(4, 5, 3); + replay.insertItem_(p0, p1, list, rtree); + replay.insertItem_(p1, p2, list, rtree); + replay.insertItem_(p2, p3, list, rtree); + replay.insertItem_(p3, p0, list, rtree); + }); + + it('identifies simple polygons', function() { + var simple = replay.isSimple_(list, rtree); + expect(simple).to.be(true); + }); + + it('identifies self-intersecting polygons', function() { + var p4 = replay.createPoint_(2, 5, 4); + var p5 = replay.createPoint_(4, 2, 5); + replay.insertItem_(p0, p4, list, rtree); + replay.insertItem_(p4, p5, list, rtree); + replay.insertItem_(p5, p0, list, rtree); + var simple = replay.isSimple_(list, rtree); + expect(simple).to.be(false); + }); + }); + }); +}); diff --git a/test/spec/ol/render/webgl/replay.test.js b/test/spec/ol/render/webgl/replay.test.js deleted file mode 100644 index 1e5b866934..0000000000 --- a/test/spec/ol/render/webgl/replay.test.js +++ /dev/null @@ -1,693 +0,0 @@ -goog.provide('ol.test.render.webgl.Replay'); - -goog.require('ol.geom.LineString'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); -goog.require('ol.render.webgl.ImageReplay'); -goog.require('ol.render.webgl.LineStringReplay'); -goog.require('ol.render.webgl.PolygonReplay'); -goog.require('ol.structs.LinkedList'); -goog.require('ol.structs.RBush'); -goog.require('ol.style.Fill'); -goog.require('ol.style.Image'); -goog.require('ol.style.Stroke'); - - -describe('ol.render.webgl.ImageReplay', function() { - var replay; - - var createImageStyle = function(image) { - var imageStyle = new ol.style.Image({ - opacity: 0.1, - rotateWithView: true, - rotation: 1.5, - scale: 2.0 - }); - imageStyle.getAnchor = function() { - return [0.5, 1]; - }; - imageStyle.getImage = function() { - return image; - }; - imageStyle.getHitDetectionImage = function() { - return image; - }; - imageStyle.getImageSize = function() { - return [512, 512]; - }; - imageStyle.getHitDetectionImageSize = function() { - return [512, 512]; - }; - imageStyle.getOrigin = function() { - return [200, 200]; - }; - imageStyle.getSize = function() { - return [256, 256]; - }; - return imageStyle; - }; - - beforeEach(function() { - var tolerance = 0.1; - var maxExtent = [-10000, -20000, 10000, 20000]; - replay = new ol.render.webgl.ImageReplay(tolerance, maxExtent); - }); - - describe('#setImageStyle', function() { - - var imageStyle1, imageStyle2; - - beforeEach(function() { - imageStyle1 = createImageStyle(new Image()); - imageStyle2 = createImageStyle(new Image()); - }); - - it('set expected states', function() { - replay.setImageStyle(imageStyle1); - expect(replay.anchorX_).to.be(0.5); - expect(replay.anchorY_).to.be(1); - expect(replay.height_).to.be(256); - expect(replay.imageHeight_).to.be(512); - expect(replay.imageWidth_).to.be(512); - expect(replay.opacity_).to.be(0.1); - expect(replay.originX_).to.be(200); - expect(replay.originY_).to.be(200); - expect(replay.rotation_).to.be(1.5); - expect(replay.rotateWithView_).to.be(true); - expect(replay.scale_).to.be(2.0); - expect(replay.width_).to.be(256); - expect(replay.images_).to.have.length(1); - expect(replay.groupIndices_).to.have.length(0); - expect(replay.hitDetectionImages_).to.have.length(1); - expect(replay.hitDetectionGroupIndices_).to.have.length(0); - - replay.setImageStyle(imageStyle1); - expect(replay.images_).to.have.length(1); - expect(replay.groupIndices_).to.have.length(0); - expect(replay.hitDetectionImages_).to.have.length(1); - expect(replay.hitDetectionGroupIndices_).to.have.length(0); - - replay.setImageStyle(imageStyle2); - expect(replay.images_).to.have.length(2); - expect(replay.groupIndices_).to.have.length(1); - expect(replay.hitDetectionImages_).to.have.length(2); - expect(replay.hitDetectionGroupIndices_).to.have.length(1); - }); - }); - - describe('#drawPoint', function() { - beforeEach(function() { - var imageStyle = createImageStyle(new Image()); - replay.setImageStyle(imageStyle); - }); - - it('sets the buffer data', function() { - var point; - - point = new ol.geom.Point([1000, 2000]); - replay.drawPoint(point, null); - expect(replay.vertices).to.have.length(32); - expect(replay.indices).to.have.length(6); - expect(replay.indices[0]).to.be(0); - expect(replay.indices[1]).to.be(1); - expect(replay.indices[2]).to.be(2); - expect(replay.indices[3]).to.be(0); - expect(replay.indices[4]).to.be(2); - expect(replay.indices[5]).to.be(3); - - point = new ol.geom.Point([2000, 3000]); - replay.drawPoint(point, null); - expect(replay.vertices).to.have.length(64); - expect(replay.indices).to.have.length(12); - expect(replay.indices[6]).to.be(4); - expect(replay.indices[7]).to.be(5); - expect(replay.indices[8]).to.be(6); - expect(replay.indices[9]).to.be(4); - expect(replay.indices[10]).to.be(6); - expect(replay.indices[11]).to.be(7); - }); - }); - - describe('#drawMultiPoint', function() { - beforeEach(function() { - var imageStyle = createImageStyle(new Image()); - replay.setImageStyle(imageStyle); - }); - - it('sets the buffer data', function() { - var multiPoint; - - multiPoint = new ol.geom.MultiPoint( - [[1000, 2000], [2000, 3000]]); - replay.drawMultiPoint(multiPoint, null); - expect(replay.vertices).to.have.length(64); - expect(replay.indices).to.have.length(12); - expect(replay.indices[0]).to.be(0); - expect(replay.indices[1]).to.be(1); - expect(replay.indices[2]).to.be(2); - expect(replay.indices[3]).to.be(0); - expect(replay.indices[4]).to.be(2); - expect(replay.indices[5]).to.be(3); - expect(replay.indices[6]).to.be(4); - expect(replay.indices[7]).to.be(5); - expect(replay.indices[8]).to.be(6); - expect(replay.indices[9]).to.be(4); - expect(replay.indices[10]).to.be(6); - expect(replay.indices[11]).to.be(7); - - multiPoint = new ol.geom.MultiPoint( - [[3000, 4000], [4000, 5000]]); - replay.drawMultiPoint(multiPoint, null); - expect(replay.vertices).to.have.length(128); - expect(replay.indices).to.have.length(24); - expect(replay.indices[12]).to.be(8); - expect(replay.indices[13]).to.be(9); - expect(replay.indices[14]).to.be(10); - expect(replay.indices[15]).to.be(8); - expect(replay.indices[16]).to.be(10); - expect(replay.indices[17]).to.be(11); - expect(replay.indices[18]).to.be(12); - expect(replay.indices[19]).to.be(13); - expect(replay.indices[20]).to.be(14); - expect(replay.indices[21]).to.be(12); - expect(replay.indices[22]).to.be(14); - expect(replay.indices[23]).to.be(15); - }); - }); -}); - -describe('ol.render.webgl.LineStringReplay', function() { - var replay; - - var strokeStyle1 = new ol.style.Stroke({ - color: [0, 255, 0, 0.4] - }); - - var strokeStyle2 = new ol.style.Stroke({ - color: [255, 0, 0, 1], - lineCap: 'square', - lineJoin: 'miter' - }); - - beforeEach(function() { - var tolerance = 0.1; - var maxExtent = [-10000, -20000, 10000, 20000]; - replay = new ol.render.webgl.LineStringReplay(tolerance, maxExtent); - }); - - describe('#setFillStrokeStyle', function() { - - it('set expected states', function() { - replay.setFillStrokeStyle(null, strokeStyle1); - expect(replay.state_).not.be(null); - expect(replay.state_.lineCap).to.be('round'); - expect(replay.state_.lineJoin).to.be('round'); - expect(replay.state_.strokeColor).to.eql([0, 1, 0, 0.4]); - expect(replay.state_.lineWidth).to.be(1); - expect(replay.state_.miterLimit).to.be(10); - expect(replay.state_.changed).to.be(true); - expect(replay.styles_).to.have.length(1); - - replay.setFillStrokeStyle(null, strokeStyle2); - expect(replay.state_.lineCap).to.be('square'); - expect(replay.state_.lineJoin).to.be('miter'); - expect(replay.state_.strokeColor).to.eql([1, 0, 0, 1]); - expect(replay.state_.lineWidth).to.be(1); - expect(replay.state_.miterLimit).to.be(10); - expect(replay.state_.changed).to.be(true); - expect(replay.styles_).to.have.length(2); - - }); - }); - - describe('#drawLineString', function() { - - it('sets the buffer data', function() { - var linestring; - - linestring = new ol.geom.LineString( - [[1000, 2000], [2000, 3000]]); - replay.setFillStrokeStyle(null, strokeStyle1); - replay.drawLineString(linestring, null); - expect(replay.vertices).to.have.length(56); - expect(replay.indices).to.have.length(18); - expect(replay.state_.changed).to.be(false); - expect(replay.startIndices).to.have.length(1); - expect(replay.startIndicesFeature).to.have.length(1); - - linestring = new ol.geom.LineString( - [[1000, 3000], [2000, 4000], [3000, 3000]]); - replay.drawLineString(linestring, null); - expect(replay.vertices).to.have.length(140); - expect(replay.indices).to.have.length(48); - expect(replay.state_.changed).to.be(false); - expect(replay.startIndices).to.have.length(2); - expect(replay.startIndicesFeature).to.have.length(2); - }); - }); - - describe('#drawMultiLineString', function() { - - it('sets the buffer data', function() { - var multilinestring; - - multilinestring = new ol.geom.MultiLineString( - [[[1000, 2000], [2000, 3000]], - [[1000, 3000], [2000, 4000], [3000, 3000]]]); - replay.setFillStrokeStyle(null, strokeStyle1); - replay.drawMultiLineString(multilinestring, null); - expect(replay.vertices).to.have.length(140); - expect(replay.indices).to.have.length(48); - expect(replay.state_.changed).to.be(false); - expect(replay.startIndices).to.have.length(1); - expect(replay.startIndicesFeature).to.have.length(1); - }); - }); - - describe('#drawCoordinates_', function() { - - it('triangulates linestrings', function() { - var linestring; - - var stroke = new ol.style.Stroke({ - color: [0, 255, 0, 1], - lineCap: 'butt', - lineJoin: 'bevel' - }); - - linestring = new ol.geom.LineString( - [[1000, 3000], [2000, 4000], [3000, 3000]]); - var flatCoordinates = linestring.getFlatCoordinates(); - replay.setFillStrokeStyle(null, stroke); - replay.drawCoordinates_(flatCoordinates, 0, - flatCoordinates.length, 2); - expect(replay.indices).to.eql( - [2, 0, 1, 4, 2, 1, - 2, 4, 3, - 5, 3, 4, 4, 6, 5]); - }); - - it('optionally creates miters', function() { - var linestring; - - var stroke = new ol.style.Stroke({ - color: [0, 255, 0, 1], - lineCap: 'butt' - }); - - linestring = new ol.geom.LineString( - [[1000, 3000], [2000, 4000], [3000, 3000]]); - var flatCoordinates = linestring.getFlatCoordinates(); - replay.setFillStrokeStyle(null, stroke); - replay.drawCoordinates_(flatCoordinates, 0, - flatCoordinates.length, 2); - expect(replay.indices).to.eql( - [2, 0, 1, 4, 2, 1, - 2, 4, 3, 3, 5, 2, - 6, 3, 4, 4, 7, 6]); - }); - - it('optionally creates caps', function() { - var linestring; - - var stroke = new ol.style.Stroke({ - color: [0, 255, 0, 1] - }); - - linestring = new ol.geom.LineString( - [[1000, 3000], [2000, 4000], [3000, 3000]]); - var flatCoordinates = linestring.getFlatCoordinates(); - replay.setFillStrokeStyle(null, stroke); - replay.drawCoordinates_(flatCoordinates, 0, - flatCoordinates.length, 2); - expect(replay.indices).to.eql( - [2, 0, 1, 1, 3, 2, - 4, 2, 3, 6, 4, 3, - 4, 6, 5, 5, 7, 4, - 8, 5, 6, 6, 9, 8, - 10, 8, 9, 9, 11, 10]); - }); - - it('respects segment orientation', function() { - var linestring; - - var stroke = new ol.style.Stroke({ - color: [0, 255, 0, 1], - lineCap: 'butt', - lineJoin: 'bevel' - }); - - linestring = new ol.geom.LineString( - [[1000, 3000], [2000, 2000], [3000, 3000]]); - var flatCoordinates = linestring.getFlatCoordinates(); - replay.setFillStrokeStyle(null, stroke); - replay.drawCoordinates_(flatCoordinates, 0, - flatCoordinates.length, 2); - expect(replay.indices).to.eql( - [2, 0, 1, 4, 2, 0, - 2, 4, 3, - 5, 3, 4, 4, 6, 5]); - }); - - it('closes boundaries', function() { - var linestring; - - var stroke = new ol.style.Stroke({ - color: [0, 255, 0, 1], - lineCap: 'butt', - lineJoin: 'bevel' - }); - - linestring = new ol.geom.LineString( - [[1000, 3000], [2000, 4000], [3000, 3000], [1000, 3000]]); - var flatCoordinates = linestring.getFlatCoordinates(); - replay.setFillStrokeStyle(null, stroke); - replay.drawCoordinates_(flatCoordinates, 0, - flatCoordinates.length, 2); - expect(replay.indices).to.eql( - [0, 2, 1, 3, 1, 2, - 5, 3, 2, - 3, 5, 4, 6, 4, 5, - 8, 6, 5, - 6, 8, 7, 9, 7, 8, - 10, 9, 8]); - expect(replay.vertices.slice(0, 7)).to.eql( - replay.vertices.slice(-14, -7)); - expect(replay.vertices.slice(14, 21)).to.eql( - replay.vertices.slice(-7)); - }); - }); -}); - - -describe('ol.render.webgl.PolygonReplay', function() { - var replay; - - var fillStyle = new ol.style.Fill({ - color: [0, 0, 255, 0.5] - }); - var strokeStyle = new ol.style.Stroke({ - color: [0, 255, 0, 0.4] - }); - - beforeEach(function() { - var tolerance = 0.1; - var maxExtent = [-10000, -20000, 10000, 20000]; - replay = new ol.render.webgl.PolygonReplay(tolerance, maxExtent); - }); - - describe('#drawPolygon', function() { - beforeEach(function() { - replay.setFillStrokeStyle(fillStyle, strokeStyle); - }); - - it('sets the buffer data', function() { - var polygon1 = new ol.geom.Polygon( - [[[1000, 2000], [1200, 2000], [1200, 3000]]] - ); - replay.drawPolygon(polygon1, null); - expect(replay.vertices).to.have.length(6); - expect(replay.indices).to.have.length(3); - - expect(replay.vertices).to.eql([ - 1200, 3000, 1200, 2000, 1000, 2000]); - expect(replay.indices).to.eql([2, 0, 1]); - - var polygon2 = new ol.geom.Polygon( - [[[4000, 2000], [4200, 2000], [4200, 3000]]] - ); - replay.drawPolygon(polygon2, null); - expect(replay.vertices).to.have.length(12); - expect(replay.indices).to.have.length(6); - - expect(replay.vertices).to.eql([ - 1200, 3000, 1200, 2000, 1000, 2000, - 4200, 3000, 4200, 2000, 4000, 2000 - ]); - expect(replay.indices).to.eql([2, 0, 1, 5, 3, 4]); - }); - }); - - describe('#drawMultiPolygon', function() { - beforeEach(function() { - replay.setFillStrokeStyle(fillStyle, strokeStyle); - }); - - it('sets the buffer data', function() { - var multiPolygon = new ol.geom.MultiPolygon([ - [[[1000, 2000], [1200, 2000], [1200, 3000]]], - [[[4000, 2000], [4200, 2000], [4200, 3000]]] - ]); - replay.drawMultiPolygon(multiPolygon, null); - expect(replay.vertices).to.have.length(12); - expect(replay.indices).to.have.length(6); - - expect(replay.vertices).to.eql([ - 1200, 3000, 1200, 2000, 1000, 2000, - 4200, 3000, 4200, 2000, 4000, 2000 - ]); - expect(replay.indices).to.eql([2, 0, 1, 5, 3, 4]); - }); - }); - - describe('triangulating functions', function() { - var list, rtree; - beforeEach(function() { - list = new ol.structs.LinkedList(); - rtree = new ol.structs.RBush(); - }); - - describe('#createPoint_', function() { - it('creates a WebGL polygon vertex', function() { - var p = replay.createPoint_(1, 1, 1); - expect(p.x).to.be(1); - expect(p.y).to.be(1); - expect(p.i).to.be(1); - expect(p.reflex).to.be(undefined); - }); - - it('adds the point to the vertex array', function() { - replay.createPoint_(1, 1, 1); - expect(replay.vertices.length).to.be(2); - expect(replay.vertices[0]).to.be(1); - expect(replay.vertices[1]).to.be(1); - }); - }); - - describe('#insertItem_', function() { - var p0, p1; - beforeEach(function() { - p0 = replay.createPoint_(1, 1, 1); - p1 = replay.createPoint_(2, 2, 2); - }); - - it('creates a WebGL polygon segment', function() { - var seg = replay.insertItem_(p0, p1, list, rtree); - expect(seg.p0).to.be(p0); - expect(seg.p1).to.be(p1); - }); - - it('inserts the segment into the provided linked list', function() { - var seg = replay.insertItem_(p0, p1, list, rtree); - expect(list.head_.data).to.be(seg); - }); - - it('inserts the segment into the R-Tree, if provided', function() { - replay.insertItem_(p0, p1, list); - expect(rtree.isEmpty()).to.be(true); - replay.insertItem_(p0, p1, list, rtree); - expect(rtree.isEmpty()).to.be(false); - }); - }); - - describe('#removeItem_', function() { - var s0, s1; - beforeEach(function() { - var p = replay.createPoint_(2, 2, 2); - s0 = replay.insertItem_(replay.createPoint_(1, 1, 1), - p, list, rtree); - s1 = replay.insertItem_(p, - replay.createPoint_(5, 2, 3), list, rtree); - }); - - it('removes the current item', function() { - replay.removeItem_(s0, s1, list, rtree); - expect(list.head_.data).not.to.be(s1); - expect(rtree.getAll().length).to.be(1); - }); - - it('updates the preceding segment', function() { - var dataExtent = rtree.getExtent(); - replay.removeItem_(s0, s1, list, rtree); - expect(s0.p1).to.be(s1.p1); - expect(rtree.getExtent()).to.eql(dataExtent); - }); - }); - - describe('#getPointsInTriangle_', function() { - var p0, p1, p2, p3; - beforeEach(function() { - p0 = replay.createPoint_(2, 0, 0); - p1 = replay.createPoint_(0, 5, 1); - p2 = replay.createPoint_(2, 3, 2); - p3 = replay.createPoint_(4, 5, 3); - replay.insertItem_(p0, p1, list, rtree); - replay.insertItem_(p1, p2, list, rtree); - replay.insertItem_(p2, p3, list, rtree); - replay.insertItem_(p3, p0, list, rtree); - replay.classifyPoints_(list, rtree, false); - }); - - it('gets every point in a triangle', function() { - var points = replay.getPointsInTriangle_({x: -3, y: 6}, {x: 7, y: 6}, - {x: 2, y: 2}, rtree); - expect(points).to.eql([p1, p2, p3]); - }); - - it('gets only reflex points in a triangle', function() { - var points = replay.getPointsInTriangle_({x: -3, y: 6}, {x: 7, y: 6}, - {x: 2, y: 2}, rtree, true); - expect(points).to.eql([p2]); - }); - }); - - describe('#getIntersections_', function() { - var p0, p1, p2, p3, s0, s1, s2, s3; - beforeEach(function() { - p0 = replay.createPoint_(2, 0, 0); - p1 = replay.createPoint_(0, 5, 1); - p2 = replay.createPoint_(2, 3, 2); - p3 = replay.createPoint_(4, 5, 3); - s0 = replay.insertItem_(p0, p1, list, rtree); - s1 = replay.insertItem_(p1, p2, list, rtree); - s2 = replay.insertItem_(p2, p3, list, rtree); - s3 = replay.insertItem_(p3, p0, list, rtree); - }); - - it('gets intersecting, but non touching segments', function() { - var segments = replay.getIntersections_({p0: {x: 0, y: 3}, p1: {x: 4, y: 5}}, - rtree); - expect(segments).to.eql([s0, s1]); - }); - - it('gets intersecting and touching segments', function() { - var segments = replay.getIntersections_({p0: {x: 0, y: 3}, p1: {x: 4, y: 5}}, - rtree, true); - expect(segments).to.eql([s0, s1, s2, s3]); - }); - }); - - describe('#calculateIntersection_', function() { - var p0 = {x: 0, y: 0}; - var p1 = {x: 4, y: 4}; - var p2 = {x: 0, y: 4}; - var p3 = {x: 4, y: 0}; - - it('calculates the intersection point of two intersecting segments', function() { - var i = replay.calculateIntersection_(p0, p1, p2, p3); - var t = replay.calculateIntersection_(p0, p1, p1, p2); - expect(i).to.eql([2, 2]); - expect(t).to.be(undefined); - }); - - it('calculates the intersection point of two touching segments', function() { - var t = replay.calculateIntersection_(p0, p1, p1, p2, true); - expect(t).to.eql([4, 4]); - }); - }); - - describe('#diagonalIsInside_', function() { - var p0, p1, p2, p3; - beforeEach(function() { - p0 = replay.createPoint_(2, 0, 0); - p1 = replay.createPoint_(0, 5, 1); - p2 = replay.createPoint_(2, 3, 2); - p3 = replay.createPoint_(4, 5, 3); - replay.insertItem_(p0, p1, list, rtree); - replay.insertItem_(p1, p2, list, rtree); - replay.insertItem_(p2, p3, list, rtree); - replay.insertItem_(p3, p0, list, rtree); - replay.classifyPoints_(list, rtree, false); - }); - - it('identifies if diagonal is inside the polygon', function() { - var inside = replay.diagonalIsInside_(p1, p2, p3, p0, p1); - expect(inside).to.be(true); - }); - - it('identifies if diagonal is outside the polygon', function() { - var inside = replay.diagonalIsInside_(p0, p1, p2, p3, p0); - expect(inside).to.be(false); - }); - }); - - describe('#classifyPoints_', function() { - var p0, p1, p2, p3; - beforeEach(function() { - p0 = replay.createPoint_(2, 0, 0); - p1 = replay.createPoint_(0, 5, 1); - p2 = replay.createPoint_(2, 3, 2); - p3 = replay.createPoint_(4, 5, 3); - replay.insertItem_(p0, p1, list, rtree); - replay.insertItem_(p1, p2, list, rtree); - replay.insertItem_(p2, p3, list, rtree); - replay.insertItem_(p3, p0, list, rtree); - }); - - it('classifies the points of clockwise polygons', function() { - replay.classifyPoints_(list, rtree, false); - expect(p0.reflex).to.be(false); - expect(p1.reflex).to.be(false); - expect(p2.reflex).to.be(true); - expect(p3.reflex).to.be(false); - }); - - it('classifies the points of counter-clockwise polygons', function() { - replay.classifyPoints_(list, rtree, true); - expect(p0.reflex).to.be(true); - expect(p1.reflex).to.be(true); - expect(p2.reflex).to.be(false); - expect(p3.reflex).to.be(true); - }); - - it('removes collinear points', function() { - replay.insertItem_(p3, p0, list, rtree); - replay.classifyPoints_(list, rtree, false); - expect(list.getLength()).to.be(4); - expect(rtree.getAll().length).to.be(4); - }); - }); - - describe('#isSimple_', function() { - var p0, p1, p2, p3; - beforeEach(function() { - p0 = replay.createPoint_(2, 0, 0); - p1 = replay.createPoint_(0, 5, 1); - p2 = replay.createPoint_(2, 3, 2); - p3 = replay.createPoint_(4, 5, 3); - replay.insertItem_(p0, p1, list, rtree); - replay.insertItem_(p1, p2, list, rtree); - replay.insertItem_(p2, p3, list, rtree); - replay.insertItem_(p3, p0, list, rtree); - }); - - it('identifies simple polygons', function() { - var simple = replay.isSimple_(list, rtree); - expect(simple).to.be(true); - }); - - it('identifies self-intersecting polygons', function() { - var p4 = replay.createPoint_(2, 5, 4); - var p5 = replay.createPoint_(4, 2, 5); - replay.insertItem_(p0, p4, list, rtree); - replay.insertItem_(p4, p5, list, rtree); - replay.insertItem_(p5, p0, list, rtree); - var simple = replay.isSimple_(list, rtree); - expect(simple).to.be(false); - }); - }); - }); -}); From 36df9a9c096641e45bd0f362270fc4d35e875acb Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 2 Nov 2016 11:19:38 +0100 Subject: [PATCH 72/88] Add missing requires --- src/ol/render/webgl/circlereplay/index.js | 1 + src/ol/render/webgl/linestringreplay/index.js | 1 + src/ol/render/webgl/polygonreplay/index.js | 2 ++ src/ol/render/webgl/replay.js | 3 +++ 4 files changed, 7 insertions(+) diff --git a/src/ol/render/webgl/circlereplay/index.js b/src/ol/render/webgl/circlereplay/index.js index 9a0c7a93b8..5ab2677c5d 100644 --- a/src/ol/render/webgl/circlereplay/index.js +++ b/src/ol/render/webgl/circlereplay/index.js @@ -1,6 +1,7 @@ goog.provide('ol.render.webgl.CircleReplay'); goog.require('ol'); +goog.require('ol.array'); goog.require('ol.color'); goog.require('ol.extent'); goog.require('ol.obj'); diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js index 7725243405..c6c2cfb5b7 100644 --- a/src/ol/render/webgl/linestringreplay/index.js +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -1,6 +1,7 @@ goog.provide('ol.render.webgl.LineStringReplay'); goog.require('ol'); +goog.require('ol.array'); goog.require('ol.color'); goog.require('ol.extent'); goog.require('ol.geom.flat.orient'); diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js index 7d18de928d..106940d6e0 100644 --- a/src/ol/render/webgl/polygonreplay/index.js +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -1,9 +1,11 @@ goog.provide('ol.render.webgl.PolygonReplay'); goog.require('ol'); +goog.require('ol.array'); goog.require('ol.color'); goog.require('ol.extent'); goog.require('ol.obj'); +goog.require('ol.geom.flat.contains'); goog.require('ol.geom.flat.orient'); goog.require('ol.geom.flat.transform'); goog.require('ol.render.webgl.polygonreplay.defaultshader'); diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js index cde240a930..e416278826 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -1,8 +1,11 @@ goog.provide('ol.render.webgl.Replay'); goog.require('ol'); +goog.require('ol.extent'); goog.require('ol.render.VectorContext'); goog.require('ol.transform'); +goog.require('ol.vec.Mat4'); +goog.require('ol.webgl'); /** * @constructor From 62fb8d8fd8b933b6b3c7210b1470e1b475b0f7a6 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 2 Nov 2016 12:24:51 +0100 Subject: [PATCH 73/88] Fix typos --- src/ol/render/webgl/imagereplay/index.js | 2 +- src/ol/render/webgl/linestringreplay/index.js | 4 +-- src/ol/render/webgl/polygonreplay/index.js | 28 +++++++++---------- src/ol/render/webgl/replay.js | 6 ++-- .../ol/render/webgl/linestringreplay.test.js | 14 +++++----- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index 8a67843502..22deaa3616 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -452,7 +452,7 @@ ol.render.webgl.ImageReplay.prototype.setUpProgram = function(gl, context, size, /** * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.shutDownProgram_ = function(gl, locations) { +ol.render.webgl.ImageReplay.prototype.shutDownProgram = function(gl, locations) { gl.disableVertexAttribArray(locations.a_position); gl.disableVertexAttribArray(locations.a_offsets); gl.disableVertexAttribArray(locations.a_texCoord); diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js index c6c2cfb5b7..3e71e3b2ff 100644 --- a/src/ol/render/webgl/linestringreplay/index.js +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -109,7 +109,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord if (closed) { //A closed line! Complete the circle. p0 = [flatCoordinates[end - stride * 2], - flatCoordinates[end - stride * 2 + 1]]; + flatCoordinates[end - stride * 2 + 1]]; startCoords = p2; } else { @@ -476,7 +476,7 @@ ol.render.webgl.LineStringReplay.prototype.setUpProgram = function(gl, context, /** * @inheritDoc */ -ol.render.webgl.LineStringReplay.prototype.shutDownProgram_ = function(gl, locations) { +ol.render.webgl.LineStringReplay.prototype.shutDownProgram = function(gl, locations) { gl.disableVertexAttribArray(locations.a_lastPos); gl.disableVertexAttribArray(locations.a_position); gl.disableVertexAttribArray(locations.a_nextPos); diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js index 106940d6e0..36b5b13ab5 100644 --- a/src/ol/render/webgl/polygonreplay/index.js +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -136,13 +136,13 @@ ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( 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)]); + Math.max(p0.y, p1.y)]); maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; p0 = p1; } segments.push(this.insertItem_(p1, start, list)); extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); + Math.max(p0.y, p1.y)]); } else { var end = flatCoordinates.length - stride; start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++); @@ -152,13 +152,13 @@ ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( 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)]); + Math.max(p0.y, p1.y)]); maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; p0 = p1; } segments.push(this.insertItem_(p1, start, list)); extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); + Math.max(p0.y, p1.y)]); } rtree.load(extents, segments); @@ -360,7 +360,7 @@ ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple //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) { + 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; @@ -431,9 +431,9 @@ ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = functio 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); + 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); + Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1); } this.indices[numIndices++] = seg.p0.i; @@ -513,7 +513,7 @@ ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) { 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); + 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); @@ -524,7 +524,7 @@ ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) { 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); + 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); @@ -574,7 +574,7 @@ ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt 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); + Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg); } return seg; }; @@ -612,7 +612,7 @@ ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, 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, + 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]) { @@ -621,7 +621,7 @@ ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, 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)) { + p2.x, p2.y], 0, 6, 2, p.x, p.y)) { result.push(p); } } @@ -642,7 +642,7 @@ ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rt 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)]); + 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) { @@ -861,7 +861,7 @@ ol.render.webgl.PolygonReplay.prototype.setUpProgram = function(gl, context, siz /** * @inheritDoc */ -ol.render.webgl.PolygonReplay.prototype.shutDownProgram_ = function(gl, locations) { +ol.render.webgl.PolygonReplay.prototype.shutDownProgram = function(gl, locations) { gl.disableVertexAttribArray(locations.a_position); }; diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js index e416278826..d9df4d015a 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -152,12 +152,12 @@ ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixe ol.render.webgl.linestringreplay.defaultshader.Locations| ol.render.webgl.polygonreplay.defaultshader.Locations} locations Locations. */ -ol.render.webgl.Replay.prototype.shutDownProgram_ = function(gl, locations) {}; +ol.render.webgl.Replay.prototype.shutDownProgram = function(gl, locations) {}; /** * @abstract - * @private + * @protected * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. * @param {Object.} skippedFeaturesHash Ids of features @@ -329,7 +329,7 @@ ol.render.webgl.Replay.prototype.replay = function(context, } // disable the vertex attrib arrays - this.shutDownProgram_(gl, locations); + this.shutDownProgram(gl, locations); if (this.lineStringReplay) { if (!tmpStencil) { diff --git a/test/spec/ol/render/webgl/linestringreplay.test.js b/test/spec/ol/render/webgl/linestringreplay.test.js index 8616694960..93fff1c28d 100644 --- a/test/spec/ol/render/webgl/linestringreplay.test.js +++ b/test/spec/ol/render/webgl/linestringreplay.test.js @@ -81,8 +81,8 @@ describe('ol.render.webgl.LineStringReplay', function() { var multilinestring; multilinestring = new ol.geom.MultiLineString( - [[[1000, 2000], [2000, 3000]], - [[1000, 3000], [2000, 4000], [3000, 3000]]]); + [[[1000, 2000], [2000, 3000]], + [[1000, 3000], [2000, 4000], [3000, 3000]]]); replay.setFillStrokeStyle(null, strokeStyle1); replay.drawMultiLineString(multilinestring, null); expect(replay.vertices).to.have.length(140); @@ -111,7 +111,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); expect(replay.indices).to.eql( - [2, 0, 1, 4, 2, 1, + [2, 0, 1, 4, 2, 1, 2, 4, 3, 5, 3, 4, 4, 6, 5]); }); @@ -131,7 +131,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); expect(replay.indices).to.eql( - [2, 0, 1, 4, 2, 1, + [2, 0, 1, 4, 2, 1, 2, 4, 3, 3, 5, 2, 6, 3, 4, 4, 7, 6]); }); @@ -150,7 +150,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); expect(replay.indices).to.eql( - [2, 0, 1, 1, 3, 2, + [2, 0, 1, 1, 3, 2, 4, 2, 3, 6, 4, 3, 4, 6, 5, 5, 7, 4, 8, 5, 6, 6, 9, 8, @@ -173,7 +173,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); expect(replay.indices).to.eql( - [2, 0, 1, 4, 2, 0, + [2, 0, 1, 4, 2, 0, 2, 4, 3, 5, 3, 4, 4, 6, 5]); }); @@ -194,7 +194,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); expect(replay.indices).to.eql( - [0, 2, 1, 3, 1, 2, + [0, 2, 1, 3, 1, 2, 5, 3, 2, 3, 5, 4, 6, 4, 5, 8, 6, 5, From c675a332353e0a3415c1d6ca0b1c6312550ff044 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 2 Nov 2016 17:08:56 +0100 Subject: [PATCH 74/88] Add tests for ol.render.webgl.Replay --- test/spec/ol/render/webgl/index.test.js | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/spec/ol/render/webgl/index.test.js b/test/spec/ol/render/webgl/index.test.js index 0e257a936e..5426879bb3 100644 --- a/test/spec/ol/render/webgl/index.test.js +++ b/test/spec/ol/render/webgl/index.test.js @@ -1 +1,62 @@ goog.provide('ol.test.render.webgl.Replay'); + +goog.require('ol.render.webgl.Replay'); + +describe('ol.render.Replay', function() { + var replay; + beforeEach(function() { + replay = new ol.render.webgl.Replay(5, [-180, -90, 180, 90]); + }); + + + describe('constructor', function() { + it('stores view related data', function() { + expect(replay.tolerance).to.be(5); + expect(replay.maxExtent).to.eql([-180, -90, 180, 90]); + expect(replay.origin).to.eql([0, 0]); + }); + + it ('sets up the required matrices', function() { + var mat3 = [1, 0, 0, 1, 0, 0]; + var mat4 = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + expect(replay.projectionMatrix_).to.eql(mat3); + expect(replay.offsetRotateMatrix_).to.eql(mat3); + expect(replay.offsetScaleMatrix_).to.eql(mat3); + expect(replay.tmpMat4_).to.eql(mat4); + }); + }); + + describe('#replay', function() { + var gl = { + uniformMatrix4fv: function() {}, + uniform1f: function() {} + }; + var context = { + bindBuffer: function() {}, + getGL: function() { + return gl; + } + }; + beforeEach(function() { + replay.setUpProgram = function() { + return { + u_projectionMatrix: true, + u_offsetScaleMatrix: true, + u_offsetRotateMatrix: true, + u_opacity: true + }; + }; + }); + + it('calculates the correct matrices', function() { + var sin = Math.sin(Math.PI); + replay.replay(context, [0, 0], 10, Math.PI, [10, 10], 1, 0, {}, undefined, + false, undefined); + + expect(replay.projectionMatrix_).to.eql([-0.02, -sin * 0.02, sin * 0.02, + -0.02, 0, 0]); + expect(replay.offsetRotateMatrix_).to.eql([-1, -sin, sin, -1, 0, 0]); + expect(replay.offsetScaleMatrix_).to.eql([0.2, 0, 0, 0.2, 0, 0]); + }); + }); +}); From 0cb0ea2a8a1334cbead698bbfa5c56c5546a9d2c Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 2 Nov 2016 18:38:18 +0100 Subject: [PATCH 75/88] Add tests for ol.render.webgl.CircleReplay --- .../spec/ol/render/webgl/circlereplay.test.js | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 test/spec/ol/render/webgl/circlereplay.test.js diff --git a/test/spec/ol/render/webgl/circlereplay.test.js b/test/spec/ol/render/webgl/circlereplay.test.js new file mode 100644 index 0000000000..e81058b618 --- /dev/null +++ b/test/spec/ol/render/webgl/circlereplay.test.js @@ -0,0 +1,95 @@ +goog.provide('ol.test.render.webgl.CircleReplay'); + +goog.require('ol.geom.Circle'); +goog.require('ol.render.webgl.CircleReplay'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); + +describe('ol.render.webgl.CircleReplay', function() { + var replay; + + var strokeStyle = new ol.style.Stroke({ + color: [0, 255, 0, 0.4] + }); + + var fillStyle = new ol.style.Fill({ + color: [255, 0, 0, 1] + }); + + beforeEach(function() { + var tolerance = 0.1; + var maxExtent = [-10000, -20000, 10000, 20000]; + replay = new ol.render.webgl.CircleReplay(tolerance, maxExtent); + }); + + describe('#setFillStrokeStyle', function() { + it('set expected states', function() { + replay.setFillStrokeStyle(fillStyle, strokeStyle); + expect(replay.state_).not.be(null); + expect(replay.state_.strokeColor).to.eql([0, 1, 0, 0.4]); + expect(replay.state_.lineWidth).to.be(1); + expect(replay.state_.fillColor).to.eql([1, 0, 0, 1]); + expect(replay.state_.changed).to.be(true); + expect(replay.styles_).to.have.length(1); + }); + + it('sets a transparent stroke, if none provided', function() { + replay.setFillStrokeStyle(fillStyle, null); + expect(replay.state_.strokeColor).to.eql([0, 0, 0, 0]); + }); + + it('sets a transparent fill, if none provided', function() { + replay.setFillStrokeStyle(null, strokeStyle); + expect(replay.state_.fillColor).to.eql([0, 0, 0, 0]); + }); + }); + + describe('#drawCircle', function() { + it('sets the buffer data', function() { + var circle = new ol.geom.Circle([0,0], 5000); + + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawCircle(circle, null); + expect(replay.vertices).to.have.length(12); + expect(replay.indices).to.have.length(3); + expect(replay.state_.changed).to.be(false); + expect(replay.startIndices).to.have.length(1); + expect(replay.startIndicesFeature).to.have.length(1); + expect(replay.radius_).to.be(5000); + }); + + it('does not draw if radius is zero', function() { + var circle = new ol.geom.Circle([0,0], 0); + + replay.drawCircle(circle, null); + expect(replay.vertices).to.have.length(0); + expect(replay.indices).to.have.length(0); + expect(replay.startIndices).to.have.length(0); + expect(replay.startIndicesFeature).to.have.length(0); + }); + + it('resets state and removes style if it belongs to a zero radius circle', function() { + var circle = new ol.geom.Circle([0,0], 0); + + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.setFillStrokeStyle(null, strokeStyle); + replay.drawCircle(circle, null); + expect(replay.styles_).to.have.length(1); + expect(replay.state_).not.be(null); + expect(replay.state_.strokeColor).to.eql([0, 1, 0, 0.4]); + expect(replay.state_.lineWidth).to.be(1); + expect(replay.state_.fillColor).to.eql([1, 0, 0, 1]); + expect(replay.state_.changed).to.be(false); + }); + }); + + describe('#drawCoordinates_', function() { + it('envelopes the circle into a right isosceles triangle', function() { + replay.radius_ = 5000; + replay.drawCoordinates_([0, 0], 0, 2, 2); + + expect(replay.vertices).to.eql([0, 0, 0, 5000, 0, 0, 1, 5000, 0, 0, 2, 5000]); + expect(replay.indices).to.eql([0, 1, 2]); + }); + }); +}); From 8a6b206220a78d9a53b2775d525bd2f27eb58524 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 2 Nov 2016 19:47:34 +0100 Subject: [PATCH 76/88] Add more tests --- .../spec/ol/render/webgl/circlereplay.test.js | 148 +++++++++++++++++ test/spec/ol/render/webgl/imagereplay.test.js | 76 +++++++++ .../ol/render/webgl/linestringreplay.test.js | 154 ++++++++++++++++++ .../ol/render/webgl/polygonreplay.test.js | 146 +++++++++++++++++ 4 files changed, 524 insertions(+) diff --git a/test/spec/ol/render/webgl/circlereplay.test.js b/test/spec/ol/render/webgl/circlereplay.test.js index e81058b618..8524928735 100644 --- a/test/spec/ol/render/webgl/circlereplay.test.js +++ b/test/spec/ol/render/webgl/circlereplay.test.js @@ -1,6 +1,9 @@ goog.provide('ol.test.render.webgl.CircleReplay'); +goog.require('ol'); +goog.require('ol.Feature'); goog.require('ol.geom.Circle'); +goog.require('ol.render.webgl.circlereplay.defaultshader'); goog.require('ol.render.webgl.CircleReplay'); goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); @@ -92,4 +95,149 @@ describe('ol.render.webgl.CircleReplay', function() { expect(replay.indices).to.eql([0, 1, 2]); }); }); + + describe('#setUpProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('returns the locations used by the shaders', function() { + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + expect(locations).to.be.a( + ol.render.webgl.circlereplay.defaultshader.Locations); + }); + + it('gets and compiles the shaders', function() { + sinon.spy(context, 'getProgram'); + sinon.spy(context, 'useProgram'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(context.getProgram.calledWithExactly( + ol.render.webgl.circlereplay.defaultshader.fragment, + ol.render.webgl.circlereplay.defaultshader.vertex)).to.be(true); + expect(context.useProgram.calledOnce).to.be(true); + }); + + it('initializes the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'vertexAttribPointer'); + sinon.spy(gl, 'enableVertexAttribArray'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(gl.vertexAttribPointer.callCount).to.be(gl.getAttribLocation.callCount); + expect(gl.enableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); + + describe('#shutDownProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + disableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('disables the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'disableVertexAttribArray'); + + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + replay.shutDownProgram(gl, locations); + expect(gl.disableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); + + describe('#drawReplay', function() { + var gl, context; + var feature1 = new ol.Feature({ + geometry: new ol.geom.Circle([0, 0], 5000) + }); + var feature2 = new ol.Feature({ + geometry: new ol.geom.Circle([10, 10], 5000) + }); + var feature3 = new ol.Feature({ + geometry: new ol.geom.Circle([20, 20], 5000) + }); + beforeEach(function() { + gl = {}; + context = {}; + replay.setFillStyle_ = function() {}; + replay.setStrokeStyle_ = function() {}; + replay.drawElements = function() {}; + sinon.spy(replay, 'setFillStyle_'); + sinon.spy(replay, 'setStrokeStyle_'); + sinon.spy(replay, 'drawElements'); + }); + + it('draws the elements in a single call if they have the same style', function() { + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawCircle(feature1.getGeometry(), feature1); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawCircle(feature2.getGeometry(), feature2); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawCircle(feature3.getGeometry(), feature3); + replay.startIndices.push(replay.indices.length); + + replay.drawReplay(gl, context, {}, false); + expect(replay.setFillStyle_.calledOnce).to.be(true); + expect(replay.setStrokeStyle_.calledOnce).to.be(true); + expect(replay.drawElements.calledOnce).to.be(true); + }); + + it('draws the elements in batches if there are multiple styles', function() { + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawCircle(feature1.getGeometry(), feature1); + replay.setFillStrokeStyle(fillStyle, null); + replay.drawCircle(feature2.getGeometry(), feature2); + replay.setFillStrokeStyle(strokeStyle, null); + replay.drawCircle(feature3.getGeometry(), feature3); + replay.startIndices.push(replay.indices.length); + + replay.drawReplay(gl, context, {}, false); + expect(replay.setFillStyle_.calledThrice).to.be(true); + expect(replay.setStrokeStyle_.calledThrice).to.be(true); + expect(replay.drawElements.calledThrice).to.be(true); + }); + + it('can skip elements if needed', function() { + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawCircle(feature1.getGeometry(), feature1); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawCircle(feature2.getGeometry(), feature2); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawCircle(feature3.getGeometry(), feature3); + replay.startIndices.push(replay.indices.length); + var skippedFeatHash = {}; + skippedFeatHash[ol.getUid(feature2).toString()] = true; + + replay.drawReplay(gl, context, skippedFeatHash, false); + expect(replay.setFillStyle_.calledOnce).to.be(true); + expect(replay.setStrokeStyle_.calledOnce).to.be(true); + expect(replay.drawElements.calledTwice).to.be(true); + }); + }); }); diff --git a/test/spec/ol/render/webgl/imagereplay.test.js b/test/spec/ol/render/webgl/imagereplay.test.js index 8ea1997917..7e5b1ce621 100644 --- a/test/spec/ol/render/webgl/imagereplay.test.js +++ b/test/spec/ol/render/webgl/imagereplay.test.js @@ -2,6 +2,7 @@ goog.provide('ol.test.render.webgl.ImageReplay'); goog.require('ol.geom.MultiPoint'); goog.require('ol.geom.Point'); +goog.require('ol.render.webgl.imagereplay.defaultshader'); goog.require('ol.render.webgl.ImageReplay'); goog.require('ol.style.Image'); @@ -166,4 +167,79 @@ describe('ol.render.webgl.ImageReplay', function() { expect(replay.indices[23]).to.be(15); }); }); + + describe('#setUpProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('returns the locations used by the shaders', function() { + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + expect(locations).to.be.a( + ol.render.webgl.imagereplay.defaultshader.Locations); + }); + + it('gets and compiles the shaders', function() { + sinon.spy(context, 'getProgram'); + sinon.spy(context, 'useProgram'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(context.getProgram.calledWithExactly( + ol.render.webgl.imagereplay.defaultshader.fragment, + ol.render.webgl.imagereplay.defaultshader.vertex)).to.be(true); + expect(context.useProgram.calledOnce).to.be(true); + }); + + it('initializes the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'vertexAttribPointer'); + sinon.spy(gl, 'enableVertexAttribArray'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(gl.vertexAttribPointer.callCount).to.be(gl.getAttribLocation.callCount); + expect(gl.enableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); + + describe('#shutDownProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + disableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('disables the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'disableVertexAttribArray'); + + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + replay.shutDownProgram(gl, locations); + expect(gl.disableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); }); diff --git a/test/spec/ol/render/webgl/linestringreplay.test.js b/test/spec/ol/render/webgl/linestringreplay.test.js index 93fff1c28d..219a26a1c9 100644 --- a/test/spec/ol/render/webgl/linestringreplay.test.js +++ b/test/spec/ol/render/webgl/linestringreplay.test.js @@ -1,7 +1,10 @@ goog.provide('ol.test.render.webgl.LineStringReplay'); +goog.require('ol'); +goog.require('ol.Feature'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); +goog.require('ol.render.webgl.linestringreplay.defaultshader'); goog.require('ol.render.webgl.LineStringReplay'); goog.require('ol.style.Stroke'); @@ -206,4 +209,155 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.vertices.slice(-7)); }); }); + + describe('#setUpProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('returns the locations used by the shaders', function() { + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + expect(locations).to.be.a( + ol.render.webgl.linestringreplay.defaultshader.Locations); + }); + + it('gets and compiles the shaders', function() { + sinon.spy(context, 'getProgram'); + sinon.spy(context, 'useProgram'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(context.getProgram.calledWithExactly( + ol.render.webgl.linestringreplay.defaultshader.fragment, + ol.render.webgl.linestringreplay.defaultshader.vertex)).to.be(true); + expect(context.useProgram.calledOnce).to.be(true); + }); + + it('initializes the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'vertexAttribPointer'); + sinon.spy(gl, 'enableVertexAttribArray'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(gl.vertexAttribPointer.callCount).to.be(gl.getAttribLocation.callCount); + expect(gl.enableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); + + describe('#shutDownProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + disableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('disables the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'disableVertexAttribArray'); + + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + replay.shutDownProgram(gl, locations); + expect(gl.disableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); + + describe('#drawReplay', function() { + var gl, context; + var feature1 = new ol.Feature({ + geometry: new ol.geom.LineString([[0, 0], [500, 500]]) + }); + var feature2 = new ol.Feature({ + geometry: new ol.geom.LineString([[0, 0], [500, 500]]) + }); + var feature3 = new ol.Feature({ + geometry: new ol.geom.LineString([[0, 0], [500, 500]]) + }); + beforeEach(function() { + gl = { + enable: function() {}, + disable: function() {}, + depthMask: function() {}, + depthFunc: function() {}, + clear: function() {}, + getParameter: function() {} + }; + context = {}; + replay.setStrokeStyle_ = function() {}; + replay.drawElements = function() {}; + sinon.spy(replay, 'setStrokeStyle_'); + sinon.spy(replay, 'drawElements'); + sinon.spy(gl, 'clear'); + }); + + it('draws the elements in a single call if they have the same style', function() { + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(feature1.getGeometry(), feature1); + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(feature2.getGeometry(), feature2); + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(feature3.getGeometry(), feature3); + replay.startIndices.push(replay.indices.length); + + replay.drawReplay(gl, context, {}, false); + expect(replay.setStrokeStyle_.calledOnce).to.be(true); + expect(replay.drawElements.calledOnce).to.be(true); + expect(gl.clear.called).to.be(true); + }); + + it('draws the elements in batches if there are multiple styles', function() { + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(feature1.getGeometry(), feature1); + replay.setFillStrokeStyle(null, strokeStyle2); + replay.drawLineString(feature2.getGeometry(), feature2); + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(feature3.getGeometry(), feature3); + replay.startIndices.push(replay.indices.length); + + replay.drawReplay(gl, context, {}, false); + expect(replay.setStrokeStyle_.calledThrice).to.be(true); + expect(replay.drawElements.calledThrice).to.be(true); + expect(gl.clear.called).to.be(true); + }); + + it('can skip elements if needed', function() { + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(feature1.getGeometry(), feature1); + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(feature2.getGeometry(), feature2); + replay.setFillStrokeStyle(null, strokeStyle1); + replay.drawLineString(feature3.getGeometry(), feature3); + replay.startIndices.push(replay.indices.length); + var skippedFeatHash = {}; + skippedFeatHash[ol.getUid(feature2).toString()] = true; + + replay.drawReplay(gl, context, skippedFeatHash, false); + expect(replay.setStrokeStyle_.calledOnce).to.be(true); + expect(replay.drawElements.calledTwice).to.be(true); + expect(gl.clear.called).to.be(true); + }); + }); }); diff --git a/test/spec/ol/render/webgl/polygonreplay.test.js b/test/spec/ol/render/webgl/polygonreplay.test.js index 449a56ab3f..62f7f8e1cd 100644 --- a/test/spec/ol/render/webgl/polygonreplay.test.js +++ b/test/spec/ol/render/webgl/polygonreplay.test.js @@ -1,7 +1,10 @@ goog.provide('ol.test.render.webgl.PolygonReplay'); +goog.require('ol'); +goog.require('ol.Feature'); goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Polygon'); +goog.require('ol.render.webgl.polygonreplay.defaultshader'); goog.require('ol.render.webgl.PolygonReplay'); goog.require('ol.structs.LinkedList'); goog.require('ol.structs.RBush'); @@ -315,4 +318,147 @@ describe('ol.render.webgl.PolygonReplay', function() { }); }); }); + + describe('#setUpProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('returns the locations used by the shaders', function() { + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + expect(locations).to.be.a( + ol.render.webgl.polygonreplay.defaultshader.Locations); + }); + + it('gets and compiles the shaders', function() { + sinon.spy(context, 'getProgram'); + sinon.spy(context, 'useProgram'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(context.getProgram.calledWithExactly( + ol.render.webgl.polygonreplay.defaultshader.fragment, + ol.render.webgl.polygonreplay.defaultshader.vertex)).to.be(true); + expect(context.useProgram.calledOnce).to.be(true); + }); + + it('initializes the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'vertexAttribPointer'); + sinon.spy(gl, 'enableVertexAttribArray'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(gl.vertexAttribPointer.callCount).to.be(gl.getAttribLocation.callCount); + expect(gl.enableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); + + describe('#shutDownProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + disableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('disables the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'disableVertexAttribArray'); + + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + replay.shutDownProgram(gl, locations); + expect(gl.disableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); + + describe('#drawReplay', function() { + var gl, context; + var feature1 = new ol.Feature({ + geometry: new ol.geom.Polygon([[[0, 0], [500, 500], [500, 0], [0, 0]]]) + }); + var feature2 = new ol.Feature({ + geometry: new ol.geom.Polygon([[[0, 0], [500, 500], [500, 0], [0, 0]]]) + }); + var feature3 = new ol.Feature({ + geometry: new ol.geom.Polygon([[[0, 0], [500, 500], [500, 0], [0, 0]]]) + }); + beforeEach(function() { + gl = {}; + context = {}; + replay.setFillStyle_ = function() {}; + replay.drawElements = function() {}; + sinon.spy(replay, 'setFillStyle_'); + sinon.spy(replay, 'drawElements'); + }); + + it('draws the elements in a single call if they have the same style', function() { + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawPolygon(feature1.getGeometry(), feature1); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawPolygon(feature2.getGeometry(), feature2); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawPolygon(feature3.getGeometry(), feature3); + replay.startIndices.push(replay.indices.length); + + replay.drawReplay(gl, context, {}, false); + expect(replay.setFillStyle_.calledOnce).to.be(true); + expect(replay.drawElements.calledOnce).to.be(true); + }); + + it('draws the elements in batches if there are multiple fill styles', function() { + var fillStyle2 = new ol.style.Fill({ + color: [0, 255, 0, 1] + }); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawPolygon(feature1.getGeometry(), feature1); + replay.setFillStrokeStyle(fillStyle2, strokeStyle); + replay.drawPolygon(feature2.getGeometry(), feature2); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawPolygon(feature3.getGeometry(), feature3); + replay.startIndices.push(replay.indices.length); + + replay.drawReplay(gl, context, {}, false); + expect(replay.setFillStyle_.calledThrice).to.be(true); + expect(replay.drawElements.calledThrice).to.be(true); + }); + + it('can skip elements if needed', function() { + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawPolygon(feature1.getGeometry(), feature1); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawPolygon(feature2.getGeometry(), feature2); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawPolygon(feature3.getGeometry(), feature3); + replay.startIndices.push(replay.indices.length); + var skippedFeatHash = {}; + skippedFeatHash[ol.getUid(feature2).toString()] = true; + + replay.drawReplay(gl, context, skippedFeatHash, false); + expect(replay.setFillStyle_.calledOnce).to.be(true); + expect(replay.drawElements.calledTwice).to.be(true); + }); + }); }); From 0c45b52a7a2c84f9302b3761cd6046f107593a3f Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Thu, 3 Nov 2016 15:58:44 +0100 Subject: [PATCH 77/88] Add functions to WebGL Immediate API --- src/ol/render/webgl/immediate.js | 147 +++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/src/ol/render/webgl/immediate.js b/src/ol/render/webgl/immediate.js index b7cddd0da2..f3e637cbeb 100644 --- a/src/ol/render/webgl/immediate.js +++ b/src/ol/render/webgl/immediate.js @@ -65,6 +65,18 @@ ol.render.webgl.Immediate = function(context, center, resolution, rotation, size */ this.imageStyle_ = null; + /** + * @private + * @type {ol.style.Fill} + */ + this.fillStyle_ = null; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.strokeStyle_ = null; + }; ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); @@ -77,6 +89,7 @@ ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); * @api */ ol.render.webgl.Immediate.prototype.setStyle = function(style) { + this.setFillStrokeStyle(style.getFill(), style.getStroke()); this.setImageStyle(style.getImage()); }; @@ -94,12 +107,27 @@ ol.render.webgl.Immediate.prototype.drawGeometry = function(geometry) { 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: ol.DEBUG && console.assert(false, 'Unsupported geometry type: ' + type); } @@ -179,9 +207,128 @@ ol.render.webgl.Immediate.prototype.drawMultiPoint = function(geometry, data) { }; +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawLineString = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); + replay.setFillStrokeStyle(null, this.strokeStyle_); + replay.drawLineString(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawMultiLineString = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); + replay.setFillStrokeStyle(null, this.strokeStyle_); + replay.drawMultiLineString(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawPolygon = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawPolygon(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawMultiPolygon = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawMultiPolygon(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawCircle = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.CircleReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.CIRCLE)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawCircle(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + /** * @inheritDoc */ ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) { this.imageStyle_ = imageStyle; }; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + this.fillStyle_ = fillStyle; + this.strokeStyle_ = strokeStyle; +}; From 310fabe94a35f2b83dc9563d7def813250a46651 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Thu, 3 Nov 2016 19:11:17 +0100 Subject: [PATCH 78/88] Add tests for WebGL Immediate API --- test/spec/ol/render/webgl/immediate.test.js | 269 ++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 test/spec/ol/render/webgl/immediate.test.js diff --git a/test/spec/ol/render/webgl/immediate.test.js b/test/spec/ol/render/webgl/immediate.test.js new file mode 100644 index 0000000000..5520258985 --- /dev/null +++ b/test/spec/ol/render/webgl/immediate.test.js @@ -0,0 +1,269 @@ +goog.provide('ol.test.render.webgl.Immediate'); + +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.Circle'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.render.webgl.CircleReplay'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.render.webgl.PolygonReplay'); +goog.require('ol.style.Circle'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); + +describe('ol.render.webgl.Immediate', function() { + var context; + var style = new ol.style.Style({ + image: new ol.style.Circle(), + fill: new ol.style.Fill(), + stroke: new ol.style.Stroke() + }); + var circle = new ol.geom.Circle([0, 0], 5); + var line = new ol.geom.LineString([[0, 0], [5, 5]]); + var multiLine = new ol.geom.MultiLineString([[[0, 0], [5, 5]]]); + var point = new ol.geom.Point([0, 0]); + var multiPoint = new ol.geom.MultiPoint([[0, 0]]); + var polygon = new ol.geom.Polygon([[[0, 0], [5, 5], [5, 0], [0, 0]]]); + var multiPolygon = new ol.geom.MultiPolygon([[[[0, 0], [5, 5], [5, 0], [0, 0]]]]); + beforeEach(function() { + context = new ol.render.webgl.Immediate({}, [0, 0], 0, 0, [0, 0], [-180, -90, 180, 90], 1); + }); + + describe('#setStyle', function() { + it('sets the style of the context', function() { + context.setStyle(style); + expect(context.fillStyle_).to.be(style.getFill()); + expect(context.strokeStyle_).to.be(style.getStroke()); + expect(context.imageStyle_).to.be(style.getImage()); + }); + }); + + describe('#drawFeature', function() { + var feat; + beforeEach(function() { + feat = new ol.Feature({ + geometry: circle + }); + context.setStyle = function() {}; + context.drawGeometry = function() {}; + sinon.spy(context, 'setStyle'); + sinon.spy(context, 'drawGeometry'); + }); + + it('updates the style of the context', function() { + context.drawFeature(feat, style); + expect(context.setStyle.calledOnce).to.be(true); + }); + + it('draws the geometry of the feature', function() { + context.drawFeature(feat, style); + expect(context.drawGeometry.calledOnce).to.be(true); + }); + + it('does nothing if no geometry is provided', function() { + feat = new ol.Feature(); + context.drawFeature(feat, style); + expect(context.setStyle.called).to.be(false); + expect(context.drawGeometry.called).to.be(false); + }); + + it('does nothing if geometry is out of bounds', function() { + feat = new ol.Feature({ + geometry: new ol.geom.Circle([540, 540], 1) + }); + context.drawFeature(feat, style); + expect(context.setStyle.called).to.be(false); + expect(context.drawGeometry.called).to.be(false); + }); + }); + + describe('#drawGeometryCollection', function() { + var geomColl = new ol.geom.GeometryCollection([circle, point, multiPoint, + line, multiLine, polygon, multiPolygon]); + + it('draws every geometry in the collection', function() { + context.drawGeometry = function() {}; + sinon.spy(context, 'drawGeometry'); + + context.drawGeometryCollection(geomColl); + expect(context.drawGeometry.callCount).to.be(7); + }); + }); + + describe('geometry functions', function() { + function mock(ctor, geomFunc) { + var tmpObj = {}; + tmpObj.replay = ctor.prototype.replay; + ctor.prototype.replay = sinon.spy(); + tmpObj.finish = ctor.prototype.finish; + ctor.prototype.finish = sinon.spy(); + tmpObj.getDeleteResourcesFunction = ctor.prototype.getDeleteResourcesFunction; + ctor.prototype.getDeleteResourcesFunction = sinon.spy(function() { + return function() {}; + }); + sinon.spy(ctor.prototype.getDeleteResourcesFunction); + if (ctor === ol.render.webgl.ImageReplay) { + tmpObj.setImageStyle = ctor.prototype.setImageStyle; + ctor.prototype.setImageStyle = sinon.spy(); + } else { + tmpObj.setFillStrokeStyle = ctor.prototype.setFillStrokeStyle; + ctor.prototype.setFillStrokeStyle = sinon.spy(); + } + tmpObj[geomFunc] = ctor.prototype[geomFunc]; + ctor.prototype[geomFunc] = sinon.spy(); + return tmpObj; + } + + function restore(ctor, tmpObj) { + for (var i in tmpObj) { + ctor.prototype[i] = tmpObj[i]; + } + } + + describe('#drawPoint', function() { + var tmpObj; + beforeEach(function() { + tmpObj = mock(ol.render.webgl.ImageReplay, 'drawPoint'); + }); + + it('draws a point', function() { + context.drawGeometry(point); + expect(ol.render.webgl.ImageReplay.prototype.setImageStyle.calledOnce).to.be(true); + expect(ol.render.webgl.ImageReplay.prototype.drawPoint.calledOnce).to.be(true); + expect(ol.render.webgl.ImageReplay.prototype.finish.calledOnce).to.be(true); + expect(ol.render.webgl.ImageReplay.prototype.replay.calledOnce).to.be(true); + expect(ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction.calledOnce).to.be(true); + }); + + after(function() { + restore(ol.render.webgl.ImageReplay, tmpObj); + }); + }); + + describe('#drawMultiPoint', function() { + var tmpObj; + beforeEach(function() { + tmpObj = mock(ol.render.webgl.ImageReplay, 'drawMultiPoint'); + }); + + it('draws a multi point', function() { + context.drawGeometry(multiPoint); + expect(ol.render.webgl.ImageReplay.prototype.setImageStyle.calledOnce).to.be(true); + expect(ol.render.webgl.ImageReplay.prototype.drawMultiPoint.calledOnce).to.be(true); + expect(ol.render.webgl.ImageReplay.prototype.finish.calledOnce).to.be(true); + expect(ol.render.webgl.ImageReplay.prototype.replay.calledOnce).to.be(true); + expect(ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction.calledOnce).to.be(true); + }); + + after(function() { + restore(ol.render.webgl.ImageReplay, tmpObj); + }); + }); + + describe('#drawLineString', function() { + var tmpObj; + beforeEach(function() { + tmpObj = mock(ol.render.webgl.LineStringReplay, 'drawLineString'); + }); + + it('draws a line string', function() { + context.drawGeometry(line); + expect(ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle.calledOnce).to.be(true); + expect(ol.render.webgl.LineStringReplay.prototype.drawLineString.calledOnce).to.be(true); + expect(ol.render.webgl.LineStringReplay.prototype.finish.calledOnce).to.be(true); + expect(ol.render.webgl.LineStringReplay.prototype.replay.calledOnce).to.be(true); + expect(ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction.calledOnce).to.be(true); + }); + + after(function() { + restore(ol.render.webgl.LineStringReplay, tmpObj); + }); + }); + + describe('#drawMultiLineString', function() { + var tmpObj; + beforeEach(function() { + tmpObj = mock(ol.render.webgl.LineStringReplay, 'drawMultiLineString'); + }); + + it('draws a multi line string', function() { + context.drawGeometry(multiLine); + expect(ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle.calledOnce).to.be(true); + expect(ol.render.webgl.LineStringReplay.prototype.drawMultiLineString.calledOnce).to.be(true); + expect(ol.render.webgl.LineStringReplay.prototype.finish.calledOnce).to.be(true); + expect(ol.render.webgl.LineStringReplay.prototype.replay.calledOnce).to.be(true); + expect(ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction.calledOnce).to.be(true); + }); + + after(function() { + restore(ol.render.webgl.LineStringReplay, tmpObj); + }); + }); + + describe('#drawPolygon', function() { + var tmpObj; + beforeEach(function() { + tmpObj = mock(ol.render.webgl.PolygonReplay, 'drawPolygon'); + }); + + it('draws a polygon', function() { + context.drawGeometry(polygon); + expect(ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle.calledOnce).to.be(true); + expect(ol.render.webgl.PolygonReplay.prototype.drawPolygon.calledOnce).to.be(true); + expect(ol.render.webgl.PolygonReplay.prototype.finish.calledOnce).to.be(true); + expect(ol.render.webgl.PolygonReplay.prototype.replay.calledOnce).to.be(true); + expect(ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction.calledOnce).to.be(true); + }); + + after(function() { + restore(ol.render.webgl.PolygonReplay, tmpObj); + }); + }); + + describe('#drawMultiPolygon', function() { + var tmpObj; + beforeEach(function() { + tmpObj = mock(ol.render.webgl.PolygonReplay, 'drawMultiPolygon'); + }); + + it('draws a multi polygon', function() { + context.drawGeometry(multiPolygon); + expect(ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle.calledOnce).to.be(true); + expect(ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon.calledOnce).to.be(true); + expect(ol.render.webgl.PolygonReplay.prototype.finish.calledOnce).to.be(true); + expect(ol.render.webgl.PolygonReplay.prototype.replay.calledOnce).to.be(true); + expect(ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction.calledOnce).to.be(true); + }); + + after(function() { + restore(ol.render.webgl.PolygonReplay, tmpObj); + }); + }); + + describe('#drawCircle', function() { + var tmpObj; + beforeEach(function() { + tmpObj = mock(ol.render.webgl.CircleReplay, 'drawCircle'); + }); + + it('draws a circle', function() { + context.drawGeometry(circle); + expect(ol.render.webgl.CircleReplay.prototype.setFillStrokeStyle.calledOnce).to.be(true); + expect(ol.render.webgl.CircleReplay.prototype.drawCircle.calledOnce).to.be(true); + expect(ol.render.webgl.CircleReplay.prototype.finish.calledOnce).to.be(true); + expect(ol.render.webgl.CircleReplay.prototype.replay.calledOnce).to.be(true); + expect(ol.render.webgl.CircleReplay.prototype.getDeleteResourcesFunction.calledOnce).to.be(true); + }); + + after(function() { + restore(ol.render.webgl.CircleReplay, tmpObj); + }); + }); + }); +}); From ca1414b2d009ff048e6bfe70f2f849474c28eb45 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 11 Nov 2016 14:58:17 +0100 Subject: [PATCH 79/88] Add depth test to PolygonReplay --- src/ol/render/webgl/polygonreplay/index.js | 19 +++++++++++++++++++ .../ol/render/webgl/polygonreplay.test.js | 9 ++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js index 36b5b13ab5..937d87ed9a 100644 --- a/src/ol/render/webgl/polygonreplay/index.js +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -870,6 +870,16 @@ ol.render.webgl.PolygonReplay.prototype.shutDownProgram = function(gl, locations * @inheritDoc */ ol.render.webgl.PolygonReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } + if (!ol.obj.isEmpty(skippedFeaturesHash)) { this.drawReplaySkipping_(gl, context, skippedFeaturesHash); } else { @@ -887,6 +897,13 @@ ol.render.webgl.PolygonReplay.prototype.drawReplay = function(gl, context, skipp end = start; } } + if (!hitDetection) { + gl.disable(gl.DEPTH_TEST); + gl.clear(gl.DEPTH_BUFFER_BIT); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } }; @@ -964,6 +981,7 @@ ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, conte if (skippedFeaturesHash[featureUid]) { if (start !== end) { this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); } end = featureStart; } @@ -972,6 +990,7 @@ ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, conte } if (start !== end) { this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); } start = end = groupStart; } diff --git a/test/spec/ol/render/webgl/polygonreplay.test.js b/test/spec/ol/render/webgl/polygonreplay.test.js index 62f7f8e1cd..e6b4e55064 100644 --- a/test/spec/ol/render/webgl/polygonreplay.test.js +++ b/test/spec/ol/render/webgl/polygonreplay.test.js @@ -406,7 +406,14 @@ describe('ol.render.webgl.PolygonReplay', function() { geometry: new ol.geom.Polygon([[[0, 0], [500, 500], [500, 0], [0, 0]]]) }); beforeEach(function() { - gl = {}; + gl = { + getParameter: function() {}, + enable: function() {}, + disable: function() {}, + depthMask: function() {}, + depthFunc: function() {}, + clear: function() {} + }; context = {}; replay.setFillStyle_ = function() {}; replay.drawElements = function() {}; From a7ddda7d8116d921dd66728eb2fc86b4f2444a95 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 14 Nov 2016 14:07:34 +0100 Subject: [PATCH 80/88] Force close polygon contours (WebGL) --- src/ol/render/webgl/linestringreplay/index.js | 10 ++++++++++ .../ol/render/webgl/polygonreplay.test.js | 20 +++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js index 3e71e3b2ff..911ca33955 100644 --- a/src/ol/render/webgl/linestringreplay/index.js +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -352,10 +352,20 @@ ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiL */ 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); } diff --git a/test/spec/ol/render/webgl/polygonreplay.test.js b/test/spec/ol/render/webgl/polygonreplay.test.js index e6b4e55064..d67431fc59 100644 --- a/test/spec/ol/render/webgl/polygonreplay.test.js +++ b/test/spec/ol/render/webgl/polygonreplay.test.js @@ -37,25 +37,25 @@ describe('ol.render.webgl.PolygonReplay', function() { [[[1000, 2000], [1200, 2000], [1200, 3000]]] ); replay.drawPolygon(polygon1, null); - expect(replay.vertices).to.have.length(6); + expect(replay.vertices).to.have.length(8); expect(replay.indices).to.have.length(3); expect(replay.vertices).to.eql([ - 1200, 3000, 1200, 2000, 1000, 2000]); + 1000, 2000, 1200, 3000, 1200, 2000, 1000, 2000]); expect(replay.indices).to.eql([2, 0, 1]); var polygon2 = new ol.geom.Polygon( [[[4000, 2000], [4200, 2000], [4200, 3000]]] ); replay.drawPolygon(polygon2, null); - expect(replay.vertices).to.have.length(12); + expect(replay.vertices).to.have.length(16); expect(replay.indices).to.have.length(6); expect(replay.vertices).to.eql([ - 1200, 3000, 1200, 2000, 1000, 2000, - 4200, 3000, 4200, 2000, 4000, 2000 + 1000, 2000, 1200, 3000, 1200, 2000, 1000, 2000, + 4000, 2000, 4200, 3000, 4200, 2000, 4000, 2000 ]); - expect(replay.indices).to.eql([2, 0, 1, 5, 3, 4]); + expect(replay.indices).to.eql([2, 0, 1, 6, 4, 5]); }); }); @@ -70,14 +70,14 @@ describe('ol.render.webgl.PolygonReplay', function() { [[[4000, 2000], [4200, 2000], [4200, 3000]]] ]); replay.drawMultiPolygon(multiPolygon, null); - expect(replay.vertices).to.have.length(12); + expect(replay.vertices).to.have.length(16); expect(replay.indices).to.have.length(6); expect(replay.vertices).to.eql([ - 1200, 3000, 1200, 2000, 1000, 2000, - 4200, 3000, 4200, 2000, 4000, 2000 + 1000, 2000, 1200, 3000, 1200, 2000, 1000, 2000, + 4000, 2000, 4200, 3000, 4200, 2000, 4000, 2000 ]); - expect(replay.indices).to.eql([2, 0, 1, 5, 3, 4]); + expect(replay.indices).to.eql([2, 0, 1, 6, 4, 5]); }); }); From dd905ffb5bd9d2c023ef75c9731c6e8e1979d4b8 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 14 Nov 2016 16:10:55 +0100 Subject: [PATCH 81/88] Cleanup and fix DPR in WebGL CircleReplay --- .../webgl/circlereplay/defaultshader.glsl | 35 ++++++++++--------- .../webgl/circlereplay/defaultshader.js | 32 ++++++++--------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/ol/render/webgl/circlereplay/defaultshader.glsl b/src/ol/render/webgl/circlereplay/defaultshader.glsl index 184155e717..dc85372ca6 100644 --- a/src/ol/render/webgl/circlereplay/defaultshader.glsl +++ b/src/ol/render/webgl/circlereplay/defaultshader.glsl @@ -6,6 +6,7 @@ varying vec2 v_center; varying vec2 v_offset; varying float v_halfWidth; +varying float v_pixelRatio; //! VERTEX @@ -17,19 +18,21 @@ uniform mat4 u_projectionMatrix; uniform mat4 u_offsetScaleMatrix; uniform mat4 u_offsetRotateMatrix; uniform float u_lineWidth; +uniform float u_pixelRatio; void main(void) { mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; v_center = vec4(u_projectionMatrix * vec4(a_position, 0., 1.)).xy; + v_pixelRatio = u_pixelRatio; float newX, newY; - float lineWidth = u_lineWidth; + float lineWidth = u_lineWidth * u_pixelRatio; + v_halfWidth = lineWidth / 2.0; if (lineWidth == 0.0) { - lineWidth = 2.0; + lineWidth = 2.0 * u_pixelRatio; } - v_halfWidth = u_lineWidth / 2.0; vec2 offset; // Radius with anitaliasing (roughly). - float radius = a_radius + 3.0; + float radius = a_radius + 3.0 * u_pixelRatio; // Until we get gl_VertexID in WebGL, we store an instruction. if (a_instruction == 0.0) { newX = a_position.x - radius; @@ -62,35 +65,35 @@ uniform float u_opacity; uniform vec4 u_fillColor; uniform vec4 u_strokeColor; uniform vec2 u_size; -uniform float u_pixelRatio; void main(void) { - vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * u_pixelRatio, - (v_center.y + 1.0) / 2.0 * u_size.y * u_pixelRatio); - vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * u_pixelRatio, - (v_offset.y + 1.0) / 2.0 * u_size.y * u_pixelRatio); + vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * v_pixelRatio, + (v_center.y + 1.0) / 2.0 * u_size.y * v_pixelRatio); + vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * v_pixelRatio, + (v_offset.y + 1.0) / 2.0 * u_size.y * v_pixelRatio); float radius = length(windowCenter - windowOffset); float dist = length(windowCenter - gl_FragCoord.xy); - if (dist > (radius + v_halfWidth) * u_pixelRatio) { + if (dist > radius + v_halfWidth) { if (u_strokeColor.a == 0.0) { gl_FragColor = u_fillColor; } else { gl_FragColor = u_strokeColor; } - gl_FragColor.a = gl_FragColor.a - (dist - (radius + v_halfWidth) * u_pixelRatio); + gl_FragColor.a = gl_FragColor.a - (dist - (radius + v_halfWidth)); } else if (u_fillColor.a == 0.0) { // Hooray, no fill, just stroke. We can use real antialiasing. gl_FragColor = u_strokeColor; - if (dist < (radius - v_halfWidth) * u_pixelRatio) { - gl_FragColor.a = gl_FragColor.a - ((radius - v_halfWidth) * u_pixelRatio - dist); + if (dist < radius - v_halfWidth) { + gl_FragColor.a = gl_FragColor.a - (radius - v_halfWidth - dist); } } else { gl_FragColor = u_fillColor; - float strokeDist = (radius - v_halfWidth) * u_pixelRatio; + float strokeDist = radius - v_halfWidth; + float antialias = 2.0 * v_pixelRatio; if (dist > strokeDist) { gl_FragColor = u_strokeColor; - } else if (dist >= strokeDist - 2.0) { - float step = smoothstep(strokeDist - 2.0, strokeDist, dist); + } else if (dist >= strokeDist - antialias) { + float step = smoothstep(strokeDist - antialias, strokeDist, dist); gl_FragColor = mix(u_fillColor, u_strokeColor, step); } } diff --git a/src/ol/render/webgl/circlereplay/defaultshader.js b/src/ol/render/webgl/circlereplay/defaultshader.js index 6a79040e7c..bdf3b549eb 100644 --- a/src/ol/render/webgl/circlereplay/defaultshader.js +++ b/src/ol/render/webgl/circlereplay/defaultshader.js @@ -21,14 +21,14 @@ ol.inherits(ol.render.webgl.circlereplay.defaultshader.Fragment, ol.webgl.Fragme * @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;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_fillColor;\nuniform vec4 u_strokeColor;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_center.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_offset.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n float radius = length(windowCenter - windowOffset);\n float dist = length(windowCenter - gl_FragCoord.xy);\n if (dist > (radius + v_halfWidth) * u_pixelRatio) {\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) * u_pixelRatio);\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) * u_pixelRatio) {\n gl_FragColor.a = gl_FragColor.a - ((radius - v_halfWidth) * u_pixelRatio - dist);\n }\n } else {\n gl_FragColor = u_fillColor;\n float strokeDist = (radius - v_halfWidth) * u_pixelRatio;\n if (dist > strokeDist) {\n gl_FragColor = u_strokeColor;\n } else if (dist >= strokeDist - 2.0) {\n float step = smoothstep(strokeDist - 2.0, 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'; +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;uniform float k;uniform vec4 l;uniform vec4 m;uniform vec2 n;uniform float o;void main(void){vec2 windowCenter=vec2((a.x+1.0)/2.0*n.x*o,(a.y+1.0)/2.0*n.y*o);vec2 windowOffset=vec2((b.x+1.0)/2.0*n.x*o,(b.y+1.0)/2.0*n.y*o);float radius=length(windowCenter-windowOffset);float dist=length(windowCenter-gl_FragCoord.xy);if(dist>(radius+c)*o){if(m.a==0.0){gl_FragColor=l;}else{gl_FragColor=m;}gl_FragColor.a=gl_FragColor.a-(dist-(radius+c)*o);}else if(l.a==0.0){gl_FragColor=m;if(dist<(radius-c)*o){gl_FragColor.a=gl_FragColor.a-((radius-c)*o-dist);}} else{gl_FragColor=l;float strokeDist=(radius-c)*o;if(dist>strokeDist){gl_FragColor=m;}else if(dist>=strokeDist-2.0){float step=smoothstep(strokeDist-2.0,strokeDist,dist);gl_FragColor=mix(l,m,step);}} gl_FragColor.a=gl_FragColor.a*k;if(gl_FragColor.a<=0.0){discard;}}'; +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;}}'; /** @@ -58,14 +58,14 @@ 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;\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;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n v_center = vec4(u_projectionMatrix * vec4(a_position, 0., 1.)).xy;\n float newX, newY;\n float lineWidth = u_lineWidth;\n if (lineWidth == 0.0) {\n lineWidth = 2.0;\n }\n v_halfWidth = u_lineWidth / 2.0;\n vec2 offset;\n // Radius with anitaliasing (roughly).\n float radius = a_radius + 3.0;\n // Until we get gl_VertexID in WebGL, we store an instruction.\n if (a_instruction == 0.0) {\n newX = a_position.x - radius;\n newY = a_position.y - radius;\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(-lineWidth, -lineWidth);\n } else {\n float sqrtVal = sqrt(2.0) + 1.0;\n if (a_instruction == 1.0) {\n newX = a_position.x + sqrtVal * radius;\n newY = a_position.y - radius;\n offset = vec2(lineWidth * sqrtVal, -lineWidth);\n } else {\n newX = a_position.x - radius;\n newY = a_position.y + sqrtVal * radius;\n offset = vec2(-lineWidth, lineWidth * sqrtVal);\n }\n }\n\n gl_Position = u_projectionMatrix * vec4(newX, newY, 0., 1.) + offsetMatrix *\n vec4(offset, 0., 0.);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y, 0., 1.)).xy;\n}\n\n\n'; +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., 1.)).xy;\n v_pixelRatio = u_pixelRatio;\n float newX, newY;\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 newX = a_position.x - radius;\n newY = a_position.y - radius;\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(-lineWidth, -lineWidth);\n } else {\n float sqrtVal = sqrt(2.0) + 1.0;\n if (a_instruction == 1.0) {\n newX = a_position.x + sqrtVal * radius;\n newY = a_position.y - radius;\n offset = vec2(lineWidth * sqrtVal, -lineWidth);\n } else {\n newX = a_position.x - radius;\n newY = a_position.y + sqrtVal * radius;\n offset = vec2(-lineWidth, lineWidth * sqrtVal);\n }\n }\n\n gl_Position = u_projectionMatrix * vec4(newX, newY, 0., 1.) + offsetMatrix *\n vec4(offset, 0., 0.);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y, 0., 1.)).xy;\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying vec2 b;varying float c;attribute vec2 d;attribute float e;attribute float f;uniform mat4 g;uniform mat4 h;uniform mat4 i;uniform float j;void main(void){mat4 offsetMatrix=h*i;a=vec4(g*vec4(d,0.,1.)).xy;float newX,newY;float lineWidth=j;if(lineWidth==0.0){lineWidth=2.0;}c=j/2.0;vec2 offset;float radius=f+3.0;if(e==0.0){newX=d.x-radius;newY=d.y-radius;offset=vec2(-lineWidth,-lineWidth);}else{float sqrtVal=sqrt(2.0)+1.0;if(e==1.0){newX=d.x+sqrtVal*radius;newY=d.y-radius;offset=vec2(lineWidth*sqrtVal,-lineWidth);}else{newX=d.x-radius;newY=d.y+sqrtVal*radius;offset=vec2(-lineWidth,lineWidth*sqrtVal);}} gl_Position=g*vec4(newX,newY,0.,1.)+offsetMatrix*vec4(offset,0.,0.);b=vec4(g*vec4(d.x+f,d.y,0.,1.)).xy;}'; +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.,1.)).xy;d=l;float newX,newY;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){newX=e.x-radius;newY=e.y-radius;offset=vec2(-lineWidth,-lineWidth);}else{float sqrtVal=sqrt(2.0)+1.0;if(f==1.0){newX=e.x+sqrtVal*radius;newY=e.y-radius;offset=vec2(lineWidth*sqrtVal,-lineWidth);}else{newX=e.x-radius;newY=e.y+sqrtVal*radius;offset=vec2(-lineWidth,lineWidth*sqrtVal);}} gl_Position=h*vec4(newX,newY,0.,1.)+offsetMatrix*vec4(offset,0.,0.);b=vec4(h*vec4(e.x+g,e.y,0.,1.)).xy;}'; /** @@ -92,71 +92,71 @@ ol.render.webgl.circlereplay.defaultshader.Locations = function(gl, program) { * @type {WebGLUniformLocation} */ this.u_fillColor = gl.getUniformLocation( - program, ol.DEBUG ? 'u_fillColor' : 'l'); + program, ol.DEBUG ? 'u_fillColor' : 'n'); /** * @type {WebGLUniformLocation} */ this.u_lineWidth = gl.getUniformLocation( - program, ol.DEBUG ? 'u_lineWidth' : 'j'); + program, ol.DEBUG ? 'u_lineWidth' : 'k'); /** * @type {WebGLUniformLocation} */ this.u_offsetRotateMatrix = gl.getUniformLocation( - program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'i'); + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'j'); /** * @type {WebGLUniformLocation} */ this.u_offsetScaleMatrix = gl.getUniformLocation( - program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'h'); + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i'); /** * @type {WebGLUniformLocation} */ this.u_opacity = gl.getUniformLocation( - program, ol.DEBUG ? 'u_opacity' : 'k'); + program, ol.DEBUG ? 'u_opacity' : 'm'); /** * @type {WebGLUniformLocation} */ this.u_pixelRatio = gl.getUniformLocation( - program, ol.DEBUG ? 'u_pixelRatio' : 'o'); + program, ol.DEBUG ? 'u_pixelRatio' : 'l'); /** * @type {WebGLUniformLocation} */ this.u_projectionMatrix = gl.getUniformLocation( - program, ol.DEBUG ? 'u_projectionMatrix' : 'g'); + program, ol.DEBUG ? 'u_projectionMatrix' : 'h'); /** * @type {WebGLUniformLocation} */ this.u_size = gl.getUniformLocation( - program, ol.DEBUG ? 'u_size' : 'n'); + program, ol.DEBUG ? 'u_size' : 'p'); /** * @type {WebGLUniformLocation} */ this.u_strokeColor = gl.getUniformLocation( - program, ol.DEBUG ? 'u_strokeColor' : 'm'); + program, ol.DEBUG ? 'u_strokeColor' : 'o'); /** * @type {number} */ this.a_instruction = gl.getAttribLocation( - program, ol.DEBUG ? 'a_instruction' : 'e'); + program, ol.DEBUG ? 'a_instruction' : 'f'); /** * @type {number} */ this.a_position = gl.getAttribLocation( - program, ol.DEBUG ? 'a_position' : 'd'); + program, ol.DEBUG ? 'a_position' : 'e'); /** * @type {number} */ this.a_radius = gl.getAttribLocation( - program, ol.DEBUG ? 'a_radius' : 'f'); + program, ol.DEBUG ? 'a_radius' : 'g'); }; From efbc4ead8d3b2b9a00b449d381eb9fbce6a4fec5 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Mon, 14 Nov 2016 17:35:26 +0100 Subject: [PATCH 82/88] Minor fixes according to review --- src/ol/render/webgl/circlereplay/index.js | 2 +- src/ol/render/webgl/index.js | 2 +- src/ol/render/webgl/linestringreplay/index.js | 3 +++ src/ol/render/webgl/polygonreplay/index.js | 1 + src/ol/structs/linkedlist.js | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ol/render/webgl/circlereplay/index.js b/src/ol/render/webgl/circlereplay/index.js index 5ab2677c5d..e947dc1505 100644 --- a/src/ol/render/webgl/circlereplay/index.js +++ b/src/ol/render/webgl/circlereplay/index.js @@ -164,7 +164,7 @@ ol.render.webgl.CircleReplay.prototype.finish = function(context) { */ ol.render.webgl.CircleReplay.prototype.getDeleteResourcesFunction = function(context) { // We only delete our stuff here. The shaders and the program may - // be used by other PolygonReplay instances (for other layers). And + // be used by other CircleReplay instances (for other layers). And // they will be deleted when disposing of the ol.webgl.Context // object. ol.DEBUG && console.assert(this.verticesBuffer, diff --git a/src/ol/render/webgl/index.js b/src/ol/render/webgl/index.js index 564bdad94a..8dc0eb2973 100644 --- a/src/ol/render/webgl/index.js +++ b/src/ol/render/webgl/index.js @@ -61,7 +61,7 @@ ol.render.webgl.lineStringInstruction = { }; /** - * Calcualtes the orientation of a triangle based on the determinant method. + * 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. diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js index 911ca33955..a54280be12 100644 --- a/src/ol/render/webgl/linestringreplay/index.js +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -150,6 +150,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord break; } else { //For the compiler not to complain. This will never be [0, 0]. + ol.DEBUG && console.assert(p0, 'p0 should be defined'); p0 = p0 || [0, 0]; numVertices = this.addVertices_(p0, p1, [0, 0], @@ -189,6 +190,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; } + // We group CW and straight lines, thus the not so inituitive CCW checking function. sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) ? -1 : 1; @@ -232,6 +234,7 @@ ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoord if (closed) { //Link the last triangle/rhombus to the first one. //n will never be numVertices / 7 here. However, the compiler complains otherwise. + ol.DEBUG && console.assert(n, 'n should be defined'); 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; diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js index 937d87ed9a..79d8042b7b 100644 --- a/src/ol/render/webgl/polygonreplay/index.js +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -297,6 +297,7 @@ ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { // introduce touching segments. However, the original data may have some. if (!this.resolveLocalSelfIntersections_(list, rtree, true)) { // Something went wrong. + ol.DEBUG && console.assert(false, 'Unexpected simple polygon geometry'); break; } } diff --git a/src/ol/structs/linkedlist.js b/src/ol/structs/linkedlist.js index 7b51fde9fe..19fe515550 100644 --- a/src/ol/structs/linkedlist.js +++ b/src/ol/structs/linkedlist.js @@ -4,6 +4,7 @@ goog.provide('ol.structs.LinkedList'); * Creates an empty linked list structure. * * @constructor + * @struct * @param {boolean=} opt_circular The last item is connected to the first one, * and the first item to the last one. Default is true. */ From 3b2ff5a2ee15525f3777ecba176a5fd44c08a593 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 15 Nov 2016 17:11:16 +0100 Subject: [PATCH 83/88] Revamp WebGL CircleReplay --- .../webgl/circlereplay/defaultshader.glsl | 30 +++++++++++-------- .../webgl/circlereplay/defaultshader.js | 4 +-- src/ol/render/webgl/circlereplay/index.js | 17 +++++++++-- .../spec/ol/render/webgl/circlereplay.test.js | 9 +++--- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/ol/render/webgl/circlereplay/defaultshader.glsl b/src/ol/render/webgl/circlereplay/defaultshader.glsl index dc85372ca6..da09b94cbe 100644 --- a/src/ol/render/webgl/circlereplay/defaultshader.glsl +++ b/src/ol/render/webgl/circlereplay/defaultshader.glsl @@ -36,26 +36,32 @@ void main(void) { // Until we get gl_VertexID in WebGL, we store an instruction. if (a_instruction == 0.0) { newX = a_position.x - radius; - newY = a_position.y - radius; + newY = a_position.y + radius; // Offsetting the edges of the triangle by lineWidth / 2 is necessary, however // we should also leave some space for the antialiasing, thus we offset by lineWidth. + offset = vec2(-lineWidth, lineWidth); + } else if (a_instruction == 1.0) { + newX = a_position.x - radius; + newY = a_position.y - radius; offset = vec2(-lineWidth, -lineWidth); + } else if (a_instruction == 2.0) { + newX = a_position.x + radius; + newY = a_position.y - radius; + offset = vec2(lineWidth, -lineWidth); } else { - float sqrtVal = sqrt(2.0) + 1.0; - if (a_instruction == 1.0) { - newX = a_position.x + sqrtVal * radius; - newY = a_position.y - radius; - offset = vec2(lineWidth * sqrtVal, -lineWidth); - } else { - newX = a_position.x - radius; - newY = a_position.y + sqrtVal * radius; - offset = vec2(-lineWidth, lineWidth * sqrtVal); - } + newX = a_position.x + radius; + newY = a_position.y + radius; + offset = vec2(lineWidth, lineWidth); } gl_Position = u_projectionMatrix * vec4(newX, newY, 0., 1.) + offsetMatrix * vec4(offset, 0., 0.); - v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y, 0., 1.)).xy; + v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y, + 0., 1.)).xy; + + if (distance(v_center, v_offset) > 20000.0) { + gl_Position = vec4(v_center, 0., 1.); + } } diff --git a/src/ol/render/webgl/circlereplay/defaultshader.js b/src/ol/render/webgl/circlereplay/defaultshader.js index bdf3b549eb..d79e5b3b91 100644 --- a/src/ol/render/webgl/circlereplay/defaultshader.js +++ b/src/ol/render/webgl/circlereplay/defaultshader.js @@ -58,14 +58,14 @@ 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., 1.)).xy;\n v_pixelRatio = u_pixelRatio;\n float newX, newY;\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 newX = a_position.x - radius;\n newY = a_position.y - radius;\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(-lineWidth, -lineWidth);\n } else {\n float sqrtVal = sqrt(2.0) + 1.0;\n if (a_instruction == 1.0) {\n newX = a_position.x + sqrtVal * radius;\n newY = a_position.y - radius;\n offset = vec2(lineWidth * sqrtVal, -lineWidth);\n } else {\n newX = a_position.x - radius;\n newY = a_position.y + sqrtVal * radius;\n offset = vec2(-lineWidth, lineWidth * sqrtVal);\n }\n }\n\n gl_Position = u_projectionMatrix * vec4(newX, newY, 0., 1.) + offsetMatrix *\n vec4(offset, 0., 0.);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y, 0., 1.)).xy;\n}\n\n\n'; +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., 1.)).xy;\n v_pixelRatio = u_pixelRatio;\n float newX, newY;\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 newX = a_position.x - radius;\n newY = a_position.y + radius;\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(-lineWidth, lineWidth);\n } else if (a_instruction == 1.0) {\n newX = a_position.x - radius;\n newY = a_position.y - radius;\n offset = vec2(-lineWidth, -lineWidth);\n } else if (a_instruction == 2.0) {\n newX = a_position.x + radius;\n newY = a_position.y - radius;\n offset = vec2(lineWidth, -lineWidth);\n } else {\n newX = a_position.x + radius;\n newY = a_position.y + radius;\n offset = vec2(lineWidth, lineWidth);\n }\n\n gl_Position = u_projectionMatrix * vec4(newX, newY, 0., 1.) + offsetMatrix *\n vec4(offset, 0., 0.);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y,\n 0., 1.)).xy;\n\n if (distance(v_center, v_offset) > 20000.0) {\n gl_Position = vec4(v_center, 0., 1.);\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.,1.)).xy;d=l;float newX,newY;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){newX=e.x-radius;newY=e.y-radius;offset=vec2(-lineWidth,-lineWidth);}else{float sqrtVal=sqrt(2.0)+1.0;if(f==1.0){newX=e.x+sqrtVal*radius;newY=e.y-radius;offset=vec2(lineWidth*sqrtVal,-lineWidth);}else{newX=e.x-radius;newY=e.y+sqrtVal*radius;offset=vec2(-lineWidth,lineWidth*sqrtVal);}} gl_Position=h*vec4(newX,newY,0.,1.)+offsetMatrix*vec4(offset,0.,0.);b=vec4(h*vec4(e.x+g,e.y,0.,1.)).xy;}'; +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.,1.)).xy;d=l;float newX,newY;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){newX=e.x-radius;newY=e.y+radius;offset=vec2(-lineWidth,lineWidth);}else if(f==1.0){newX=e.x-radius;newY=e.y-radius;offset=vec2(-lineWidth,-lineWidth);}else if(f==2.0){newX=e.x+radius;newY=e.y-radius;offset=vec2(lineWidth,-lineWidth);}else{newX=e.x+radius;newY=e.y+radius;offset=vec2(lineWidth,lineWidth);}gl_Position=h*vec4(newX,newY,0.,1.)+offsetMatrix*vec4(offset,0.,0.);b=vec4(h*vec4(e.x+g,e.y,0.,1.)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.,1.);}}'; /** diff --git a/src/ol/render/webgl/circlereplay/index.js b/src/ol/render/webgl/circlereplay/index.js index e947dc1505..c4821560bc 100644 --- a/src/ol/render/webgl/circlereplay/index.js +++ b/src/ol/render/webgl/circlereplay/index.js @@ -96,9 +96,20 @@ ol.render.webgl.CircleReplay.prototype.drawCoordinates_ = function( this.vertices[numVertices++] = 2; this.vertices[numVertices++] = this.radius_; - this.indices[numIndices++] = n++; - this.indices[numIndices++] = n++; - this.indices[numIndices++] = n++; + 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; } }; diff --git a/test/spec/ol/render/webgl/circlereplay.test.js b/test/spec/ol/render/webgl/circlereplay.test.js index 8524928735..5b8d061e02 100644 --- a/test/spec/ol/render/webgl/circlereplay.test.js +++ b/test/spec/ol/render/webgl/circlereplay.test.js @@ -53,8 +53,8 @@ describe('ol.render.webgl.CircleReplay', function() { replay.setFillStrokeStyle(fillStyle, strokeStyle); replay.drawCircle(circle, null); - expect(replay.vertices).to.have.length(12); - expect(replay.indices).to.have.length(3); + expect(replay.vertices).to.have.length(16); + expect(replay.indices).to.have.length(6); expect(replay.state_.changed).to.be(false); expect(replay.startIndices).to.have.length(1); expect(replay.startIndicesFeature).to.have.length(1); @@ -91,8 +91,9 @@ describe('ol.render.webgl.CircleReplay', function() { replay.radius_ = 5000; replay.drawCoordinates_([0, 0], 0, 2, 2); - expect(replay.vertices).to.eql([0, 0, 0, 5000, 0, 0, 1, 5000, 0, 0, 2, 5000]); - expect(replay.indices).to.eql([0, 1, 2]); + expect(replay.vertices).to.eql([0, 0, 0, 5000, 0, 0, 1, 5000, + 0, 0, 2, 5000, 0, 0, 3, 5000]); + expect(replay.indices).to.eql([0, 1, 2, 2, 3, 0]); }); }); From 3e56dba1b45c942d22ed2c7786f5e58629e8ecc9 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 16 Nov 2016 12:54:05 +0100 Subject: [PATCH 84/88] Do not draw vectors outside viewport --- src/ol/renderer/webgl/vectorlayer.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ol/renderer/webgl/vectorlayer.js b/src/ol/renderer/webgl/vectorlayer.js index 035cb76ad1..494d5eb149 100644 --- a/src/ol/renderer/webgl/vectorlayer.js +++ b/src/ol/renderer/webgl/vectorlayer.js @@ -73,11 +73,17 @@ ol.renderer.webgl.VectorLayer.prototype.composeFrame = function(frameState, laye 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, - frameState.size, frameState.pixelRatio, layerState.opacity, + size, pixelRatio, layerState.opacity, layerState.managed ? frameState.skippedFeatureUids : {}); + gl.disable(gl.SCISSOR_TEST); } }; From 02461b3416cdaa4ad52c96c5d72671f11cffdabc Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 18 Nov 2016 15:31:40 +0100 Subject: [PATCH 85/88] Add no-op WebGL text replay --- src/ol/render/webgl/replaygroup.js | 4 +- src/ol/render/webgl/textreplay/index.js | 65 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/ol/render/webgl/textreplay/index.js diff --git a/src/ol/render/webgl/replaygroup.js b/src/ol/render/webgl/replaygroup.js index bd2e29a373..18a97e1e1f 100644 --- a/src/ol/render/webgl/replaygroup.js +++ b/src/ol/render/webgl/replaygroup.js @@ -8,6 +8,7 @@ goog.require('ol.render.webgl.CircleReplay'); goog.require('ol.render.webgl.ImageReplay'); goog.require('ol.render.webgl.LineStringReplay'); goog.require('ol.render.webgl.PolygonReplay'); +goog.require('ol.render.webgl.TextReplay'); /** * @constructor @@ -312,5 +313,6 @@ 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 + 'Polygon': ol.render.webgl.PolygonReplay, + 'Text': ol.render.webgl.TextReplay }; diff --git a/src/ol/render/webgl/textreplay/index.js b/src/ol/render/webgl/textreplay/index.js new file mode 100644 index 0000000000..c9b088c1d3 --- /dev/null +++ b/src/ol/render/webgl/textreplay/index.js @@ -0,0 +1,65 @@ +goog.provide('ol.render.webgl.TextReplay'); + +goog.require('ol'); + +/** + * @constructor + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.TextReplay = function(tolerance, maxExtent) {}; + +/** + * @param {ol.style.Text} textStyle Text style. + */ +ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) {}; + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.TextReplay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + return undefined; +}; + +/** + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, + end, stride, geometry, feature) {}; + +/** + * @abstract + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.TextReplay.prototype.finish = function(context) {}; + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) { + return ol.nullFunction; +}; From 0f204641e3eaa9224666a27313742aa30da6a7b9 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 18 Nov 2016 17:44:43 +0100 Subject: [PATCH 86/88] Clean up GLSLs somewhat --- .../webgl/circlereplay/defaultshader.glsl | 21 ++++++------------- .../webgl/circlereplay/defaultshader.js | 4 ++-- .../webgl/linestringreplay/defaultshader.glsl | 8 ++++--- .../webgl/linestringreplay/defaultshader.js | 4 ++-- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/ol/render/webgl/circlereplay/defaultshader.glsl b/src/ol/render/webgl/circlereplay/defaultshader.glsl index da09b94cbe..25dcc40b02 100644 --- a/src/ol/render/webgl/circlereplay/defaultshader.glsl +++ b/src/ol/render/webgl/circlereplay/defaultshader.glsl @@ -24,7 +24,6 @@ void main(void) { mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; v_center = vec4(u_projectionMatrix * vec4(a_position, 0., 1.)).xy; v_pixelRatio = u_pixelRatio; - float newX, newY; float lineWidth = u_lineWidth * u_pixelRatio; v_halfWidth = lineWidth / 2.0; if (lineWidth == 0.0) { @@ -35,27 +34,19 @@ void main(void) { float radius = a_radius + 3.0 * u_pixelRatio; // Until we get gl_VertexID in WebGL, we store an instruction. if (a_instruction == 0.0) { - newX = a_position.x - radius; - newY = a_position.y + radius; // Offsetting the edges of the triangle by lineWidth / 2 is necessary, however // we should also leave some space for the antialiasing, thus we offset by lineWidth. - offset = vec2(-lineWidth, lineWidth); + offset = vec2(-1.0, 1.0); } else if (a_instruction == 1.0) { - newX = a_position.x - radius; - newY = a_position.y - radius; - offset = vec2(-lineWidth, -lineWidth); + offset = vec2(-1.0, -1.0); } else if (a_instruction == 2.0) { - newX = a_position.x + radius; - newY = a_position.y - radius; - offset = vec2(lineWidth, -lineWidth); + offset = vec2(1.0, -1.0); } else { - newX = a_position.x + radius; - newY = a_position.y + radius; - offset = vec2(lineWidth, lineWidth); + offset = vec2(1.0, 1.0); } - gl_Position = u_projectionMatrix * vec4(newX, newY, 0., 1.) + offsetMatrix * - vec4(offset, 0., 0.); + gl_Position = u_projectionMatrix * vec4(a_position + offset * radius, 0., 1.) + + offsetMatrix * vec4(offset * lineWidth, 0., 0.); v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y, 0., 1.)).xy; diff --git a/src/ol/render/webgl/circlereplay/defaultshader.js b/src/ol/render/webgl/circlereplay/defaultshader.js index d79e5b3b91..6fc057099a 100644 --- a/src/ol/render/webgl/circlereplay/defaultshader.js +++ b/src/ol/render/webgl/circlereplay/defaultshader.js @@ -58,14 +58,14 @@ 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., 1.)).xy;\n v_pixelRatio = u_pixelRatio;\n float newX, newY;\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 newX = a_position.x - radius;\n newY = a_position.y + radius;\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(-lineWidth, lineWidth);\n } else if (a_instruction == 1.0) {\n newX = a_position.x - radius;\n newY = a_position.y - radius;\n offset = vec2(-lineWidth, -lineWidth);\n } else if (a_instruction == 2.0) {\n newX = a_position.x + radius;\n newY = a_position.y - radius;\n offset = vec2(lineWidth, -lineWidth);\n } else {\n newX = a_position.x + radius;\n newY = a_position.y + radius;\n offset = vec2(lineWidth, lineWidth);\n }\n\n gl_Position = u_projectionMatrix * vec4(newX, newY, 0., 1.) + offsetMatrix *\n vec4(offset, 0., 0.);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y,\n 0., 1.)).xy;\n\n if (distance(v_center, v_offset) > 20000.0) {\n gl_Position = vec4(v_center, 0., 1.);\n }\n}\n\n\n'; +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., 1.)).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., 1.) +\n offsetMatrix * vec4(offset * lineWidth, 0., 0.);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y,\n 0., 1.)).xy;\n\n if (distance(v_center, v_offset) > 20000.0) {\n gl_Position = vec4(v_center, 0., 1.);\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.,1.)).xy;d=l;float newX,newY;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){newX=e.x-radius;newY=e.y+radius;offset=vec2(-lineWidth,lineWidth);}else if(f==1.0){newX=e.x-radius;newY=e.y-radius;offset=vec2(-lineWidth,-lineWidth);}else if(f==2.0){newX=e.x+radius;newY=e.y-radius;offset=vec2(lineWidth,-lineWidth);}else{newX=e.x+radius;newY=e.y+radius;offset=vec2(lineWidth,lineWidth);}gl_Position=h*vec4(newX,newY,0.,1.)+offsetMatrix*vec4(offset,0.,0.);b=vec4(h*vec4(e.x+g,e.y,0.,1.)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.,1.);}}'; +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.,1.)).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.,1.)+offsetMatrix*vec4(offset*lineWidth,0.,0.);b=vec4(h*vec4(e.x+g,e.y,0.,1.)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.,1.);}}'; /** diff --git a/src/ol/render/webgl/linestringreplay/defaultshader.glsl b/src/ol/render/webgl/linestringreplay/defaultshader.glsl index 1658f4e9d7..7a670ae571 100644 --- a/src/ol/render/webgl/linestringreplay/defaultshader.glsl +++ b/src/ol/render/webgl/linestringreplay/defaultshader.glsl @@ -22,14 +22,16 @@ uniform float u_miterLimit; void main(void) { bool degenerate = false; - v_halfWidth = u_lineWidth / 2.0; + float direction = float(sign(a_direction)); float miterLimit = u_miterLimit + u_lineWidth; mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; vec2 offset; - v_round = 0.0; - float direction = a_direction / abs(a_direction); vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.); + + v_round = 0.0; + v_halfWidth = u_lineWidth / 2.0; v_roundVertex = projPos.xy; + if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) { vec2 dirVect = a_nextPos - a_position; vec2 normal = normalize(vec2(-dirVect.y, dirVect.x)); diff --git a/src/ol/render/webgl/linestringreplay/defaultshader.js b/src/ol/render/webgl/linestringreplay/defaultshader.js index ef95508f13..1c988f110d 100644 --- a/src/ol/render/webgl/linestringreplay/defaultshader.js +++ b/src/ol/render/webgl/linestringreplay/defaultshader.js @@ -58,14 +58,14 @@ ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vert * @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\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n float miterLimit = u_miterLimit + u_lineWidth;\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + offsetMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = offsetMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n float direction = float(sign(a_direction));\n float miterLimit = u_miterLimit + u_lineWidth;\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n vec2 offset;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n\n v_round = 0.0;\n v_halfWidth = u_lineWidth / 2.0;\n v_roundVertex = projPos.xy;\n\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + offsetMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = offsetMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\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;void main(void){bool degenerate=false;c=k/2.0;float miterLimit=l+k;mat4 offsetMatrix=i*j;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+offsetMatrix*vec4(longOffset,0.,0.);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;void main(void){bool degenerate=false;float direction=float(sign(g));float miterLimit=l+k;mat4 offsetMatrix=i*j;vec2 offset;vec4 projPos=h*vec4(e,0.,1.);a=0.0;c=k/2.0;b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+offsetMatrix*vec4(longOffset,0.,0.);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; /** From d27591dea79eacdbbb6c45567eb35a8ac561ec7e Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 22 Nov 2016 09:43:59 +0100 Subject: [PATCH 87/88] Adjust WebGL immediate test --- test/spec/ol/render/webgl/immediate.test.js | 33 +++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/test/spec/ol/render/webgl/immediate.test.js b/test/spec/ol/render/webgl/immediate.test.js index 5520258985..f3f34b44d7 100644 --- a/test/spec/ol/render/webgl/immediate.test.js +++ b/test/spec/ol/render/webgl/immediate.test.js @@ -18,21 +18,21 @@ goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); describe('ol.render.webgl.Immediate', function() { - var context; - var style = new ol.style.Style({ - image: new ol.style.Circle(), - fill: new ol.style.Fill(), - stroke: new ol.style.Stroke() - }); - var circle = new ol.geom.Circle([0, 0], 5); - var line = new ol.geom.LineString([[0, 0], [5, 5]]); - var multiLine = new ol.geom.MultiLineString([[[0, 0], [5, 5]]]); - var point = new ol.geom.Point([0, 0]); - var multiPoint = new ol.geom.MultiPoint([[0, 0]]); - var polygon = new ol.geom.Polygon([[[0, 0], [5, 5], [5, 0], [0, 0]]]); - var multiPolygon = new ol.geom.MultiPolygon([[[[0, 0], [5, 5], [5, 0], [0, 0]]]]); + var context, style, circle, line, multiLine, point, multiPoint, polygon, multiPolygon; beforeEach(function() { context = new ol.render.webgl.Immediate({}, [0, 0], 0, 0, [0, 0], [-180, -90, 180, 90], 1); + style = new ol.style.Style({ + image: new ol.style.Circle(), + fill: new ol.style.Fill(), + stroke: new ol.style.Stroke() + }); + circle = new ol.geom.Circle([0, 0], 5); + line = new ol.geom.LineString([[0, 0], [5, 5]]); + multiLine = new ol.geom.MultiLineString([[[0, 0], [5, 5]]]); + point = new ol.geom.Point([0, 0]); + multiPoint = new ol.geom.MultiPoint([[0, 0]]); + polygon = new ol.geom.Polygon([[[0, 0], [5, 5], [5, 0], [0, 0]]]); + multiPolygon = new ol.geom.MultiPolygon([[[[0, 0], [5, 5], [5, 0], [0, 0]]]]); }); describe('#setStyle', function() { @@ -84,8 +84,11 @@ describe('ol.render.webgl.Immediate', function() { }); describe('#drawGeometryCollection', function() { - var geomColl = new ol.geom.GeometryCollection([circle, point, multiPoint, - line, multiLine, polygon, multiPolygon]); + var geomColl; + beforeEach(function() { + geomColl = new ol.geom.GeometryCollection([circle, point, multiPoint, + line, multiLine, polygon, multiPolygon]); + }); it('draws every geometry in the collection', function() { context.drawGeometry = function() {}; From dba09341120e5d796ad37bad9178aa3505505010 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 23 Nov 2016 09:47:10 +0100 Subject: [PATCH 88/88] Refactor LineString GLSL & clean up --- .../webgl/circlereplay/defaultshader.glsl | 10 +- .../webgl/circlereplay/defaultshader.js | 4 +- .../webgl/imagereplay/defaultshader.glsl | 4 +- .../render/webgl/imagereplay/defaultshader.js | 4 +- .../webgl/linestringreplay/defaultshader.glsl | 177 ++++++++++-------- .../webgl/linestringreplay/defaultshader.js | 4 +- .../webgl/polygonreplay/defaultshader.glsl | 2 +- .../webgl/polygonreplay/defaultshader.js | 4 +- 8 files changed, 116 insertions(+), 93 deletions(-) diff --git a/src/ol/render/webgl/circlereplay/defaultshader.glsl b/src/ol/render/webgl/circlereplay/defaultshader.glsl index 25dcc40b02..e877879fc3 100644 --- a/src/ol/render/webgl/circlereplay/defaultshader.glsl +++ b/src/ol/render/webgl/circlereplay/defaultshader.glsl @@ -22,7 +22,7 @@ uniform float u_pixelRatio; void main(void) { mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; - v_center = vec4(u_projectionMatrix * vec4(a_position, 0., 1.)).xy; + v_center = vec4(u_projectionMatrix * vec4(a_position, 0.0, 1.0)).xy; v_pixelRatio = u_pixelRatio; float lineWidth = u_lineWidth * u_pixelRatio; v_halfWidth = lineWidth / 2.0; @@ -45,13 +45,13 @@ void main(void) { offset = vec2(1.0, 1.0); } - gl_Position = u_projectionMatrix * vec4(a_position + offset * radius, 0., 1.) + - offsetMatrix * vec4(offset * lineWidth, 0., 0.); + gl_Position = u_projectionMatrix * vec4(a_position + offset * radius, 0.0, 1.0) + + offsetMatrix * vec4(offset * lineWidth, 0.0, 0.0); v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y, - 0., 1.)).xy; + 0.0, 1.0)).xy; if (distance(v_center, v_offset) > 20000.0) { - gl_Position = vec4(v_center, 0., 1.); + gl_Position = vec4(v_center, 0.0, 1.0); } } diff --git a/src/ol/render/webgl/circlereplay/defaultshader.js b/src/ol/render/webgl/circlereplay/defaultshader.js index 6fc057099a..18ae36140a 100644 --- a/src/ol/render/webgl/circlereplay/defaultshader.js +++ b/src/ol/render/webgl/circlereplay/defaultshader.js @@ -58,14 +58,14 @@ 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., 1.)).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., 1.) +\n offsetMatrix * vec4(offset * lineWidth, 0., 0.);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y,\n 0., 1.)).xy;\n\n if (distance(v_center, v_offset) > 20000.0) {\n gl_Position = vec4(v_center, 0., 1.);\n }\n}\n\n\n'; +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.,1.)).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.,1.)+offsetMatrix*vec4(offset*lineWidth,0.,0.);b=vec4(h*vec4(e.x+g,e.y,0.,1.)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.,1.);}}'; +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);}}'; /** diff --git a/src/ol/render/webgl/imagereplay/defaultshader.glsl b/src/ol/render/webgl/imagereplay/defaultshader.glsl index 1d37462aea..b1b6168fff 100644 --- a/src/ol/render/webgl/imagereplay/defaultshader.glsl +++ b/src/ol/render/webgl/imagereplay/defaultshader.glsl @@ -22,8 +22,8 @@ void main(void) { if (a_rotateWithView == 1.0) { offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; } - vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.); - gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets; + vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0); + gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; v_texCoord = a_texCoord; v_opacity = a_opacity; } diff --git a/src/ol/render/webgl/imagereplay/defaultshader.js b/src/ol/render/webgl/imagereplay/defaultshader.js index 15630006e2..374b8a4365 100644 --- a/src/ol/render/webgl/imagereplay/defaultshader.js +++ b/src/ol/render/webgl/imagereplay/defaultshader.js @@ -58,14 +58,14 @@ ol.inherits(ol.render.webgl.imagereplay.defaultshader.Vertex, ol.webgl.Vertex); * @const * @type {string} */ -ol.render.webgl.imagereplay.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.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; +ol.render.webgl.imagereplay.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.imagereplay.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.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}'; +ol.render.webgl.imagereplay.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;}'; /** diff --git a/src/ol/render/webgl/linestringreplay/defaultshader.glsl b/src/ol/render/webgl/linestringreplay/defaultshader.glsl index 7a670ae571..ce9586a133 100644 --- a/src/ol/render/webgl/linestringreplay/defaultshader.glsl +++ b/src/ol/render/webgl/linestringreplay/defaultshader.glsl @@ -20,96 +20,119 @@ uniform mat4 u_offsetRotateMatrix; uniform float u_lineWidth; uniform float u_miterLimit; +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 - a_position; + vec2 normal = normalize(vec2(-turnDir * dirVect.y, turnDir * dirVect.x)); + offset = u_lineWidth / 2.0 * normal * direction; +} + +void miterUp(out vec2 offset, out float round, in bool isRound, in float direction) { + float halfWidth = u_lineWidth / 2.0; + vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos)); + vec2 normal = vec2(-tangent.y, tangent.x); + vec2 dirVect = a_nextPos - a_position; + 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 > u_miterLimit + u_lineWidth) { + 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(a_nextPos - a_position) + normalize(a_position - a_lastPos)); + vec2 normal = vec2(-tangent.y, tangent.x); + vec2 dirVect = a_lastPos - a_position; + vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x)); + vec2 longOffset, shortOffset, longVertex; + vec4 shortProjVertex; + float halfWidth = u_lineWidth / 2.0; + if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) { + longOffset = tmpNormal * direction * halfWidth; + shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth; + longVertex = a_nextPos; + shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0.0, 1.0); + } else { + shortOffset = tmpNormal * direction * halfWidth; + longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth; + longVertex = a_lastPos; + shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0.0, 1.0); + } + //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/). + vec4 p1 = u_projectionMatrix * 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 = a_position - 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(v_halfWidth * v_halfWidth * 2.0); + offset = normal * length; + if (isRound) { + round = 1.0; + } +} + void main(void) { bool degenerate = false; float direction = float(sign(a_direction)); - float miterLimit = u_miterLimit + u_lineWidth; mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; vec2 offset; - vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.); + vec4 projPos = u_projectionMatrix * vec4(a_position, 0.0, 1.0); + bool round = nearlyEquals(mod(a_direction, 2.0), 0.0); v_round = 0.0; v_halfWidth = u_lineWidth / 2.0; v_roundVertex = projPos.xy; - if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) { - vec2 dirVect = a_nextPos - a_position; - vec2 normal = normalize(vec2(-dirVect.y, dirVect.x)); - offset = v_halfWidth * normal * direction; - } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) { - vec2 dirVect = a_lastPos - a_position; - vec2 normal = normalize(vec2(dirVect.y, -dirVect.x)); - offset = v_halfWidth * normal * direction; - } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) { - vec2 dirVect = a_nextPos - a_position; - vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x)); - vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos)); - vec2 normal = vec2(-tangent.y, tangent.x); - float miterLength = abs(v_halfWidth / dot(normal, tmpNormal)); - if (mod(a_direction, 23.0) == 0.0) { - offset = normal * direction * miterLength; - if (mod(a_direction, 2.0) == 0.0) { - v_round = 1.0; - } else if (miterLength > miterLimit) { - offset = tmpNormal * direction * v_halfWidth; - } - } else { - dirVect = a_lastPos - a_position; - vec2 longOffset, shortOffset, longVertex; - vec4 shortProjVertex; - if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) { - longOffset = tmpNormal * direction * v_halfWidth; - shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth; - longVertex = a_nextPos; - shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.); - } else { - shortOffset = tmpNormal * direction * v_halfWidth; - longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth; - longVertex = a_lastPos; - shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.); - } - //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/). - vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + offsetMatrix * vec4(longOffset, 0., 0.); - vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0., 0.); - vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0., 0.); - vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0., 0.); - float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y); - float epsilon = 0.000000000001; - float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom; - float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom; - if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) { - gl_Position = shortProjVertex; - gl_Position.x = p1.x + firstU * (p2.x - p1.x); - gl_Position.y = p1.y + firstU * (p2.y - p1.y); - degenerate = true; - } else { - offset = normal * direction * miterLength; - } - } - } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) { - vec2 normal; - if (mod(a_direction, 7.0) == 0.0) { - vec2 dirVect = a_position - a_nextPos; - vec2 firstNormal = normalize(dirVect); - vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction); - vec2 hypotenuse = normalize(firstNormal - secondNormal); - normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction); - } else { - vec2 dirVect = a_position - a_lastPos; - vec2 firstNormal = normalize(dirVect); - vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction); - vec2 hypotenuse = normalize(firstNormal - secondNormal); - normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction); - } - float length = sqrt(v_halfWidth * v_halfWidth * 2.0); - offset = normal * length; - if (mod(a_direction, 2.0) == 0.0) { - v_round = 1.0; - } + if (nearlyEquals(mod(a_direction, 3.0), 0.0) || nearlyEquals(mod(a_direction, 17.0), 0.0)) { + alongNormal(offset, a_nextPos, 1.0, direction); + } else if (nearlyEquals(mod(a_direction, 5.0), 0.0) || nearlyEquals(mod(a_direction, 13.0), 0.0)) { + alongNormal(offset, a_lastPos, -1.0, direction); + } else if (nearlyEquals(mod(a_direction, 23.0), 0.0)) { + miterUp(offset, v_round, round, direction); + } else if (nearlyEquals(mod(a_direction, 19.0), 0.0)) { + degenerate = miterDown(offset, projPos, offsetMatrix, direction); + } else if (nearlyEquals(mod(a_direction, 7.0), 0.0)) { + squareCap(offset, v_round, round, a_nextPos, 1.0, direction); + } else if (nearlyEquals(mod(a_direction, 11.0), 0.0)) { + squareCap(offset, v_round, round, a_lastPos, -1.0, direction); } if (!degenerate) { - vec4 offsets = offsetMatrix * vec4(offset, 0., 0.); + vec4 offsets = offsetMatrix * vec4(offset, 0.0, 0.0); gl_Position = projPos + offsets; + } else { + gl_Position = vec4(offset, 0.0, 1.0); } } diff --git a/src/ol/render/webgl/linestringreplay/defaultshader.js b/src/ol/render/webgl/linestringreplay/defaultshader.js index 1c988f110d..195d07a49a 100644 --- a/src/ol/render/webgl/linestringreplay/defaultshader.js +++ b/src/ol/render/webgl/linestringreplay/defaultshader.js @@ -58,14 +58,14 @@ ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vert * @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\nvoid main(void) {\n bool degenerate = false;\n float direction = float(sign(a_direction));\n float miterLimit = u_miterLimit + u_lineWidth;\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n vec2 offset;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n\n v_round = 0.0;\n v_halfWidth = u_lineWidth / 2.0;\n v_roundVertex = projPos.xy;\n\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + offsetMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = offsetMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform 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;void main(void){bool degenerate=false;float direction=float(sign(g));float miterLimit=l+k;mat4 offsetMatrix=i*j;vec2 offset;vec4 projPos=h*vec4(e,0.,1.);a=0.0;c=k/2.0;b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+offsetMatrix*vec4(longOffset,0.,0.);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform 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);}}'; /** diff --git a/src/ol/render/webgl/polygonreplay/defaultshader.glsl b/src/ol/render/webgl/polygonreplay/defaultshader.glsl index 489fc52133..5810adb17b 100644 --- a/src/ol/render/webgl/polygonreplay/defaultshader.glsl +++ b/src/ol/render/webgl/polygonreplay/defaultshader.glsl @@ -13,7 +13,7 @@ uniform mat4 u_offsetScaleMatrix; uniform mat4 u_offsetRotateMatrix; void main(void) { - gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.); + gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0); } diff --git a/src/ol/render/webgl/polygonreplay/defaultshader.js b/src/ol/render/webgl/polygonreplay/defaultshader.js index 723f17074a..4298d2e83e 100644 --- a/src/ol/render/webgl/polygonreplay/defaultshader.js +++ b/src/ol/render/webgl/polygonreplay/defaultshader.js @@ -58,14 +58,14 @@ 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., 1.);\n}\n\n\n'; +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.,1.);}'; +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);}'; /**