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) {