diff --git a/src/ol/extent.js b/src/ol/extent.js index e20c5bfb17..1011e84acc 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -36,15 +36,20 @@ ol.Extent.boundingExtent = function(var_args) { var i; for (i = 1; i < arguments.length; ++i) { var coordinate = arguments[i]; - extent.minX = Math.min(extent.minX, coordinate.x); - extent.minY = Math.min(extent.minY, coordinate.y); - extent.maxX = Math.max(extent.maxX, coordinate.x); - extent.maxY = Math.max(extent.maxY, coordinate.y); + extent.extendXY(coordinate.x, coordinate.y); } return extent; }; +/** + * @return {ol.Extent} Empty extent. + */ +ol.Extent.createEmptyExtent = function() { + return new ol.Extent(Infinity, Infinity, -Infinity, -Infinity); +}; + + /** * @param {ol.Coordinate} center Center. * @param {number} resolution Resolution. diff --git a/src/ol/math.js b/src/ol/math.js index b1fa7f9024..8a1d41a965 100644 --- a/src/ol/math.js +++ b/src/ol/math.js @@ -29,6 +29,16 @@ ol.math.csch = function(x) { }; +/** + * @param {number} x X. + * @return {number} The smallest power of two greater than or equal to x. + */ +ol.math.roundUpToPowerOfTwo = function(x) { + goog.asserts.assert(0 < x); + return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2)); +}; + + /** * @param {number} x X. * @return {number} Hyperbolic secant of x. diff --git a/src/ol/rectangle.js b/src/ol/rectangle.js index 9d7a27af5e..b5fecc67e3 100644 --- a/src/ol/rectangle.js +++ b/src/ol/rectangle.js @@ -15,9 +15,6 @@ goog.require('ol.Size'); */ ol.Rectangle = function(minX, minY, maxX, maxY) { - goog.asserts.assert(minX <= maxX); - goog.asserts.assert(minY <= maxY); - /** * @type {number} */ @@ -55,10 +52,38 @@ ol.Rectangle.prototype.equals = function(rectangle) { * @param {ol.Rectangle} rectangle Rectangle. */ ol.Rectangle.prototype.extend = function(rectangle) { - this.minX = Math.min(this.minX, rectangle.minX); - this.minY = Math.min(this.minY, rectangle.minY); - this.maxX = Math.max(this.maxX, rectangle.maxX); - this.maxY = Math.max(this.maxY, rectangle.maxY); + if (rectangle.minX < this.minX) { + this.minX = rectangle.minX; + } + if (rectangle.minY < this.minY) { + this.minY = rectangle.minY; + } + if (rectangle.maxX > this.maxX) { + this.maxX = rectangle.maxX; + } + if (rectangle.maxY > this.maxY) { + this.maxY = rectangle.maxY; + } +}; + + +/** + * @param {number} x X. + * @param {number} y Y. + */ +ol.Rectangle.prototype.extendXY = function(x, y) { + if (x < this.minX) { + this.minX = x; + } + if (y < this.minY) { + this.minY = y; + } + if (x > this.maxX) { + this.maxX = x; + } + if (y > this.maxY) { + this.maxY = y; + } }; @@ -107,6 +132,14 @@ ol.Rectangle.prototype.intersects = function(rectangle) { }; +/** + * @return {boolean} Is empty. + */ +ol.Rectangle.prototype.isEmpty = function() { + return this.maxX < this.minX || this.maxY < this.minY; +}; + + /** * @param {ol.Coordinate} coordinate Coordinate. * @return {ol.Coordinate} Coordinate. diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js index 46d2246c85..a3c6bd6144 100644 --- a/src/ol/renderer/webgl/webglimagelayerrenderer.js +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -28,25 +28,6 @@ ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { */ this.image_ = null; - /** - * The last rendered texture. - * @private - * @type {WebGLTexture} - */ - this.texture_ = null; - - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.texCoordMatrix_ = goog.vec.Mat4.createNumberIdentity(); - - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.projectionMatrix_ = goog.vec.Mat4.createNumber(); - }; goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); @@ -63,7 +44,7 @@ ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { // http://learningwebgl.com/blog/?p=2101 var imageElement = image.getImageElement(this); - var gl = this.getMapRenderer().getGL(); + var gl = this.getWebGLMapRenderer().getGL(); var texture = gl.createTexture(); @@ -86,43 +67,6 @@ ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { }; -/** - * @inheritDoc - */ -ol.renderer.webgl.ImageLayer.prototype.disposeInternal = function() { - var mapRenderer = this.getMapRenderer(); - var gl = mapRenderer.getGL(); - if (!gl.isContextLost()) { - gl.deleteTexture(this.texture_); - } - goog.base(this, 'disposeInternal'); -}; - - -/** - * @inheritDoc - */ -ol.renderer.webgl.ImageLayer.prototype.getTexCoordMatrix = function() { - return this.texCoordMatrix_; -}; - - -/** - * @inheritDoc - */ -ol.renderer.webgl.ImageLayer.prototype.getTexture = function() { - return this.texture_; -}; - - -/** - * @inheritDoc - */ -ol.renderer.webgl.ImageLayer.prototype.getProjectionMatrix = function() { - return this.projectionMatrix_; -}; - - /** * @return {ol.layer.ImageLayer} Tile layer. */ @@ -131,21 +75,13 @@ ol.renderer.webgl.ImageLayer.prototype.getImageLayer = function() { }; -/** - * @inheritDoc - */ -ol.renderer.webgl.ImageLayer.prototype.handleWebGLContextLost = function() { - this.texture_ = null; -}; - - /** * @inheritDoc */ ol.renderer.webgl.ImageLayer.prototype.renderFrame = function(frameState, layerState) { - var gl = this.getMapRenderer().getGL(); + var gl = this.getWebGLMapRenderer().getGL(); var view2DState = frameState.view2DState; var viewCenter = view2DState.center; @@ -153,7 +89,7 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame = var viewRotation = view2DState.rotation; var image = this.image_; - var texture = this.texture_; + var texture = this.texture; var imageLayer = this.getImageLayer(); var imageSource = imageLayer.getImageSource(); @@ -171,13 +107,13 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame = } else if (imageState == ol.ImageState.LOADED) { image = image_; texture = this.createTexture_(image_); - if (!goog.isNull(this.texture_)) { + if (!goog.isNull(this.texture)) { frameState.postRenderFunctions.push( goog.partial(function(gl, texture) { if (!gl.isContextLost()) { gl.deleteTexture(texture); } - }, gl, this.texture_)); + }, gl, this.texture)); } } } @@ -186,19 +122,19 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame = if (!goog.isNull(image)) { goog.asserts.assert(!goog.isNull(texture)); - var canvas = this.getMapRenderer().getCanvas(); + var canvas = this.getWebGLMapRenderer().getCanvas(); this.updateProjectionMatrix_(canvas.width, canvas.height, viewCenter, viewResolution, viewRotation, image.getExtent()); // Translate and scale to flip the Y coord. - var texCoordMatrix = this.texCoordMatrix_; + var texCoordMatrix = this.texCoordMatrix; goog.vec.Mat4.makeIdentity(texCoordMatrix); goog.vec.Mat4.scale(texCoordMatrix, 1, -1, 1); goog.vec.Mat4.translate(texCoordMatrix, 0, -1, 0); this.image_ = image; - this.texture_ = texture; + this.texture = texture; this.updateAttributions(frameState.attributions, image.getAttributions()); } @@ -221,7 +157,7 @@ ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = var canvasExtentWidth = canvasWidth * viewResolution; var canvasExtentHeight = canvasHeight * viewResolution; - var projectionMatrix = this.projectionMatrix_; + var projectionMatrix = this.projectionMatrix; goog.vec.Mat4.makeIdentity(projectionMatrix); goog.vec.Mat4.scale(projectionMatrix, 2 / canvasExtentWidth, 2 / canvasExtentHeight, 1); diff --git a/src/ol/renderer/webgl/webgllayerrenderer.js b/src/ol/renderer/webgl/webgllayerrenderer.js index 7199246d4b..f2cabe24b0 100644 --- a/src/ol/renderer/webgl/webgllayerrenderer.js +++ b/src/ol/renderer/webgl/webgllayerrenderer.js @@ -3,6 +3,7 @@ goog.provide('ol.renderer.webgl.Layer'); goog.require('goog.vec.Mat4'); +goog.require('ol.FrameState'); goog.require('ol.layer.Layer'); goog.require('ol.renderer.Layer'); goog.require('ol.vec.Mat4'); @@ -19,6 +20,36 @@ ol.renderer.webgl.Layer = function(mapRenderer, layer) { goog.base(this, mapRenderer, layer); + /** + * @protected + * @type {WebGLTexture} + */ + this.texture = null; + + /** + * @protected + * @type {WebGLFramebuffer} + */ + this.framebuffer = null; + + /** + * @protected + * @type {number|undefined} + */ + this.framebufferDimension = undefined; + + /** + * @protected + * @type {!goog.vec.Mat4.Number} + */ + this.texCoordMatrix = goog.vec.Mat4.createNumber(); + + /** + * @protected + * @type {!goog.vec.Mat4.Number} + */ + this.projectionMatrix = goog.vec.Mat4.createNumberIdentity(); + /** * @private * @type {!goog.vec.Mat4.Float32} @@ -64,6 +95,55 @@ ol.renderer.webgl.Layer = function(mapRenderer, layer) { goog.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer); +/** + * @param {ol.FrameState} frameState Frame state. + * @param {number} framebufferDimension Framebuffer dimension. + * @protected + */ +ol.renderer.webgl.Layer.prototype.bindFramebuffer = + function(frameState, framebufferDimension) { + + var mapRenderer = this.getWebGLMapRenderer(); + var gl = mapRenderer.getGL(); + + if (!goog.isDef(this.framebufferDimension) || + this.framebufferDimension != framebufferDimension) { + + var map = this.getMap(); + frameState.postRenderFunctions.push( + goog.partial(function(gl, framebuffer, texture) { + if (!gl.isContextLost()) { + gl.deleteFramebuffer(framebuffer); + gl.deleteTexture(texture); + } + }, gl, this.framebuffer, this.texture)); + + var texture = gl.createTexture(); + gl.bindTexture(goog.webgl.TEXTURE_2D, texture); + gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, + framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA, + goog.webgl.UNSIGNED_BYTE, null); + gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, + goog.webgl.LINEAR); + gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, + goog.webgl.LINEAR); + + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER, + goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0); + + this.texture = texture; + this.framebuffer = framebuffer; + this.framebufferDimension = framebufferDimension; + + } else { + gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer); + } + +}; + + /** * @return {!goog.vec.Mat4.Float32} Color matrix. */ @@ -76,31 +156,35 @@ ol.renderer.webgl.Layer.prototype.getColorMatrix = function() { /** - * @inheritDoc - * @return {ol.renderer.Map} MapRenderer. + * @return {ol.renderer.webgl.Map} MapRenderer. */ -ol.renderer.webgl.Layer.prototype.getMapRenderer = function() { - return /** @type {ol.renderer.webgl.Map} */ (goog.base( - this, 'getMapRenderer')); +ol.renderer.webgl.Layer.prototype.getWebGLMapRenderer = function() { + return /** @type {ol.renderer.webgl.Map} */ (this.getMapRenderer()); }; /** * @return {!goog.vec.Mat4.Number} Matrix. */ -ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = goog.abstractMethod; +ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() { + return this.texCoordMatrix; +}; /** * @return {WebGLTexture} Texture. */ -ol.renderer.webgl.Layer.prototype.getTexture = goog.abstractMethod; +ol.renderer.webgl.Layer.prototype.getTexture = function() { + return this.texture; +}; /** * @return {!goog.vec.Mat4.Number} Matrix. */ -ol.renderer.webgl.Layer.prototype.getProjectionMatrix = goog.abstractMethod; +ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() { + return this.projectionMatrix; +}; /** @@ -150,7 +234,11 @@ ol.renderer.webgl.Layer.prototype.handleLayerSaturationChange = function() { /** * Handle webglcontextlost. */ -ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = goog.nullFunction; +ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() { + this.texture = null; + this.framebuffer = null; + this.framebufferDimension = undefined; +}; /** diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index 8d839d183d..11ff3b5a08 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -22,6 +22,8 @@ goog.require('ol.renderer.webgl.FragmentShader'); goog.require('ol.renderer.webgl.ImageLayer'); goog.require('ol.renderer.webgl.TileLayer'); goog.require('ol.renderer.webgl.VertexShader'); +goog.require('ol.structs.Buffer'); +goog.require('ol.structs.IntegerSet'); goog.require('ol.structs.LRUCache'); goog.require('ol.webgl'); goog.require('ol.webgl.WebGLContextEventType'); @@ -33,6 +35,14 @@ goog.require('ol.webgl.WebGLContextEventType'); ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024; +/** + * @typedef {{buf: ol.structs.Buffer, + * buffer: WebGLBuffer, + * dirtySet: ol.structs.IntegerSet}} + */ +ol.renderer.webgl.BufferCacheEntry; + + /** * @typedef {{magFilter: number, minFilter: number, texture: WebGLTexture}} */ @@ -170,9 +180,20 @@ ol.renderer.webgl.Map = function(container, map) { /** * @private - * @type {WebGLBuffer} + * @type {ol.structs.Buffer} */ - this.arrayBuffer_ = null; + this.arrayBuffer_ = new ol.structs.Buffer([ + -1, -1, 0, 0, + 1, -1, 1, 0, + -1, 1, 0, 1, + 1, 1, 1, 1 + ]); + + /** + * @private + * @type {Object.} + */ + this.bufferCache_ = {}; /** * @private @@ -216,6 +237,47 @@ ol.renderer.webgl.Map = function(container, map) { goog.inherits(ol.renderer.webgl.Map, ol.renderer.Map); +/** + * @param {number} target Target. + * @param {ol.structs.Buffer} buf Buffer. + */ +ol.renderer.webgl.Map.prototype.bindBuffer = function(target, buf) { + var gl = this.getGL(); + var arr = buf.getArray(); + var bufferKey = goog.getUid(buf); + if (bufferKey in this.bufferCache_) { + var bufferCacheEntry = this.bufferCache_[bufferKey]; + gl.bindBuffer(target, bufferCacheEntry.buffer); + bufferCacheEntry.dirtySet.forEachRange(function(start, stop) { + // FIXME check if slice is really efficient here + var slice = arr.slice(start, stop); + gl.bufferSubData( + target, + start, + target == goog.webgl.ARRAY_BUFFER ? + new Float32Array(slice) : + new Uint16Array(slice)); + }); + bufferCacheEntry.dirtySet.clear(); + } else { + var buffer = gl.createBuffer(); + gl.bindBuffer(target, buffer); + gl.bufferData( + target, + target == goog.webgl.ARRAY_BUFFER ? + new Float32Array(arr) : new Uint16Array(arr), + buf.getUsage()); + var dirtySet = new ol.structs.IntegerSet(); + buf.addDirtySet(dirtySet); + this.bufferCache_[bufferKey] = { + buf: buf, + buffer: buffer, + dirtySet: dirtySet + }; + } +}; + + /** * @param {ol.Tile} tile Tile. * @param {number} magFilter Mag filter. @@ -276,12 +338,35 @@ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { }; +/** + * @param {ol.structs.Buffer} buf Buffer. + */ +ol.renderer.webgl.Map.prototype.deleteBuffer = function(buf) { + var gl = this.getGL(); + var arr = buf.getArray(); + var bufferKey = goog.getUid(buf); + goog.asserts.assert(bufferKey in this.bufferCache_); + var bufferCacheEntry = this.bufferCache_[bufferKey]; + bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet); + if (!gl.isContextLost()) { + gl.deleteBuffer(bufferCacheEntry.buffer); + } + delete this.bufferCache_[bufferKey]; +}; + + /** * @inheritDoc */ ol.renderer.webgl.Map.prototype.disposeInternal = function() { var gl = this.getGL(); + goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) { + bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet); + }); if (!gl.isContextLost()) { + goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) { + gl.deleteBuffer(bufferCacheEntry.buffer); + }); goog.object.forEach(this.programCache_, function(program) { gl.deleteProgram(program); }); @@ -409,7 +494,7 @@ ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) { } event.preventDefault(); this.locations_ = null; - this.arrayBuffer_ = null; + this.bufferCache_ = {}; this.shaderCache_ = {}; this.programCache_ = {}; this.textureCache_.clear(); @@ -511,19 +596,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { }; } - if (goog.isNull(this.arrayBuffer_)) { - var arrayBuffer = gl.createBuffer(); - gl.bindBuffer(goog.webgl.ARRAY_BUFFER, arrayBuffer); - gl.bufferData(goog.webgl.ARRAY_BUFFER, new Float32Array([ - -1, -1, 0, 0, - 1, -1, 1, 0, - -1, 1, 0, 1, - 1, 1, 1, 1 - ]), goog.webgl.STATIC_DRAW); - this.arrayBuffer_ = arrayBuffer; - } else { - gl.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_); - } + this.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_); gl.enableVertexAttribArray(this.locations_.a_position); gl.vertexAttribPointer( diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index ec25692126..7b7a848d1a 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -2,9 +2,7 @@ // FIXME animated shaders! check in redraw goog.provide('ol.renderer.webgl.TileLayer'); -goog.provide('ol.renderer.webgl.tilelayerrenderer'); -goog.provide('ol.renderer.webgl.tilelayerrenderer.shader.Fragment'); -goog.provide('ol.renderer.webgl.tilelayerrenderer.shader.Vertex'); +goog.provide('ol.renderer.webgl.tilelayer.shader'); goog.require('goog.array'); goog.require('goog.object'); @@ -13,16 +11,17 @@ goog.require('goog.vec.Mat4'); goog.require('goog.vec.Vec4'); goog.require('goog.webgl'); goog.require('ol.Extent'); -goog.require('ol.FrameState'); goog.require('ol.Size'); goog.require('ol.Tile'); goog.require('ol.TileCoord'); goog.require('ol.TileRange'); goog.require('ol.TileState'); goog.require('ol.layer.TileLayer'); +goog.require('ol.math'); goog.require('ol.renderer.webgl.FragmentShader'); goog.require('ol.renderer.webgl.Layer'); goog.require('ol.renderer.webgl.VertexShader'); +goog.require('ol.structs.Buffer'); @@ -30,23 +29,23 @@ goog.require('ol.renderer.webgl.VertexShader'); * @constructor * @extends {ol.renderer.webgl.FragmentShader} */ -ol.renderer.webgl.tilelayerrenderer.shader.Fragment = function() { +ol.renderer.webgl.tilelayer.shader.Fragment = function() { goog.base(this, [ 'precision mediump float;', '', - 'uniform sampler2D uTexture;', + 'uniform sampler2D u_texture;', '', - 'varying vec2 vTexCoord;', + 'varying vec2 v_texCoord;', '', 'void main(void) {', - ' gl_FragColor = texture2D(uTexture, vTexCoord);', + ' gl_FragColor = texture2D(u_texture, v_texCoord);', '}' ].join('\n')); }; goog.inherits( - ol.renderer.webgl.tilelayerrenderer.shader.Fragment, + ol.renderer.webgl.tilelayer.shader.Fragment, ol.renderer.webgl.FragmentShader); -goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Fragment); +goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Fragment); @@ -54,27 +53,27 @@ goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Fragment); * @constructor * @extends {ol.renderer.webgl.VertexShader} */ -ol.renderer.webgl.tilelayerrenderer.shader.Vertex = function() { +ol.renderer.webgl.tilelayer.shader.Vertex = function() { goog.base(this, [ - 'attribute vec2 aPosition;', - 'attribute vec2 aTexCoord;', + 'attribute vec2 a_position;', + 'attribute vec2 a_texCoord;', '', - 'varying vec2 vTexCoord;', + 'varying vec2 v_texCoord;', '', - 'uniform vec4 uTileOffset;', + 'uniform vec4 u_tileOffset;', '', 'void main(void) {', - ' gl_Position.xy = aPosition * uTileOffset.xy + uTileOffset.zw;', + ' gl_Position.xy = a_position * u_tileOffset.xy + u_tileOffset.zw;', ' gl_Position.z = 0.;', ' gl_Position.w = 1.;', - ' vTexCoord = aTexCoord;', + ' v_texCoord = a_texCoord;', '}' ].join('\n')); }; goog.inherits( - ol.renderer.webgl.tilelayerrenderer.shader.Vertex, + ol.renderer.webgl.tilelayer.shader.Vertex, ol.renderer.webgl.VertexShader); -goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Vertex); +goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Vertex); @@ -93,59 +92,33 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { * @type {ol.renderer.webgl.FragmentShader} */ this.fragmentShader_ = - ol.renderer.webgl.tilelayerrenderer.shader.Fragment.getInstance(); + ol.renderer.webgl.tilelayer.shader.Fragment.getInstance(); /** * @private * @type {ol.renderer.webgl.VertexShader} */ - this.vertexShader_ = - ol.renderer.webgl.tilelayerrenderer.shader.Vertex.getInstance(); + this.vertexShader_ = ol.renderer.webgl.tilelayer.shader.Vertex.getInstance(); /** * @private - * @type {{aPosition: number, - * aTexCoord: number, - * uTileOffset: WebGLUniformLocation, - * uTexture: WebGLUniformLocation}|null} + * @type {{a_position: number, + * a_texCoord: number, + * u_tileOffset: WebGLUniformLocation, + * u_texture: WebGLUniformLocation}|null} */ this.locations_ = null; /** * @private - * @type {WebGLBuffer} + * @type {ol.structs.Buffer} */ - this.arrayBuffer_ = null; - - /** - * @private - * @type {WebGLTexture} - */ - this.texture_ = null; - - /** - * @private - * @type {WebGLFramebuffer} - */ - this.framebuffer_ = null; - - /** - * @private - * @type {number|undefined} - */ - this.framebufferDimension_ = undefined; - - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.texCoordMatrix_ = goog.vec.Mat4.createNumber(); - - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity(); + this.arrayBuffer_ = new ol.structs.Buffer([ + 0, 0, 0, 1, + 1, 0, 1, 1, + 0, 1, 0, 0, + 1, 1, 1, 0 + ]); /** * @private @@ -163,94 +136,16 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); -/** - * @param {ol.FrameState} frameState Frame state. - * @param {number} framebufferDimension Framebuffer dimension. - * @private - */ -ol.renderer.webgl.TileLayer.prototype.bindFramebuffer_ = - function(frameState, framebufferDimension) { - - var mapRenderer = this.getMapRenderer(); - var gl = mapRenderer.getGL(); - - if (!goog.isDef(this.framebufferDimension_) || - this.framebufferDimension_ != framebufferDimension) { - - var map = this.getMap(); - frameState.postRenderFunctions.push( - goog.partial(function(gl, framebuffer, texture) { - if (!gl.isContextLost()) { - gl.deleteFramebuffer(framebuffer); - gl.deleteTexture(texture); - } - }, gl, this.framebuffer_, this.texture_)); - - var texture = gl.createTexture(); - gl.bindTexture(goog.webgl.TEXTURE_2D, texture); - gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, - framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA, - goog.webgl.UNSIGNED_BYTE, null); - gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, - goog.webgl.LINEAR); - gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, - goog.webgl.LINEAR); - - var framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer); - gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER, - goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0); - - this.texture_ = texture; - this.framebuffer_ = framebuffer; - this.framebufferDimension_ = framebufferDimension; - - } else { - gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer_); - } - -}; - - /** * @inheritDoc */ ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() { - var mapRenderer = this.getMapRenderer(); - var gl = mapRenderer.getGL(); - if (!gl.isContextLost()) { - gl.deleteBuffer(this.arrayBuffer_); - gl.deleteFramebuffer(this.framebuffer_); - gl.deleteTexture(this.texture_); - } + var mapRenderer = this.getWebGLMapRenderer(); + mapRenderer.deleteBuffer(this.arrayBuffer_); goog.base(this, 'disposeInternal'); }; -/** - * @inheritDoc - */ -ol.renderer.webgl.TileLayer.prototype.getTexCoordMatrix = function() { - return this.texCoordMatrix_; -}; - - -/** - * @inheritDoc - */ -ol.renderer.webgl.TileLayer.prototype.getTexture = function() { - return this.texture_; -}; - - -/** - * @inheritDoc - */ -ol.renderer.webgl.TileLayer.prototype.getProjectionMatrix = function() { - return this.projectionMatrix_; -}; - - /** * @return {ol.layer.TileLayer} Tile layer. */ @@ -263,11 +158,8 @@ ol.renderer.webgl.TileLayer.prototype.getTileLayer = function() { * @inheritDoc */ ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() { + goog.base(this, 'handleWebGLContextLost'); this.locations_ = null; - this.arrayBuffer_ = null; - this.texture_ = null; - this.framebuffer_ = null; - this.framebufferDimension_ = undefined; }; @@ -277,7 +169,7 @@ ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() { ol.renderer.webgl.TileLayer.prototype.renderFrame = function(frameState, layerState) { - var mapRenderer = this.getMapRenderer(); + var mapRenderer = this.getWebGLMapRenderer(); var gl = mapRenderer.getGL(); var view2DState = frameState.view2DState; @@ -316,8 +208,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = var maxDimension = Math.max( tileRangeSize.width * tileSize.width, tileRangeSize.height * tileSize.height); - var framebufferDimension = - Math.pow(2, Math.ceil(Math.log(maxDimension) / Math.log(2))); + var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension); var framebufferExtentSize = new ol.Size( tileResolution * framebufferDimension, tileResolution * framebufferDimension); @@ -330,7 +221,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = minX + framebufferExtentSize.width, minY + framebufferExtentSize.height); - this.bindFramebuffer_(frameState, framebufferDimension); + this.bindFramebuffer(frameState, framebufferDimension); gl.viewport(0, 0, framebufferDimension, framebufferDimension); gl.clearColor(0, 0, 0, 0); @@ -342,34 +233,21 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = gl.useProgram(program); if (goog.isNull(this.locations_)) { this.locations_ = { - aPosition: gl.getAttribLocation(program, 'aPosition'), - aTexCoord: gl.getAttribLocation(program, 'aTexCoord'), - uTileOffset: gl.getUniformLocation(program, 'uTileOffset'), - uTexture: gl.getUniformLocation(program, 'uTexture') + a_position: gl.getAttribLocation(program, 'a_position'), + a_texCoord: gl.getAttribLocation(program, 'a_texCoord'), + u_tileOffset: gl.getUniformLocation(program, 'u_tileOffset'), + u_texture: gl.getUniformLocation(program, 'u_texture') }; } - if (goog.isNull(this.arrayBuffer_)) { - var arrayBuffer = gl.createBuffer(); - gl.bindBuffer(goog.webgl.ARRAY_BUFFER, arrayBuffer); - gl.bufferData(goog.webgl.ARRAY_BUFFER, new Float32Array([ - 0, 0, 0, 1, - 1, 0, 1, 1, - 0, 1, 0, 0, - 1, 1, 1, 0 - ]), goog.webgl.STATIC_DRAW); - this.arrayBuffer_ = arrayBuffer; - } else { - gl.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_); - } - - gl.enableVertexAttribArray(this.locations_.aPosition); + mapRenderer.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_); + gl.enableVertexAttribArray(this.locations_.a_position); gl.vertexAttribPointer( - this.locations_.aPosition, 2, goog.webgl.FLOAT, false, 16, 0); - gl.enableVertexAttribArray(this.locations_.aTexCoord); + this.locations_.a_position, 2, goog.webgl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(this.locations_.a_texCoord); gl.vertexAttribPointer( - this.locations_.aTexCoord, 2, goog.webgl.FLOAT, false, 16, 8); - gl.uniform1i(this.locations_.uTexture, 0); + this.locations_.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8); + gl.uniform1i(this.locations_.u_texture, 0); /** * @type {Object.>} @@ -424,7 +302,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = /** @type {Array.} */ var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); goog.array.sort(zs); - var uTileOffset = goog.vec.Vec4.createFloat32(); + var u_tileOffset = goog.vec.Vec4.createFloat32(); goog.array.forEach(zs, function(z) { goog.object.forEach(tilesToDrawByZ[z], function(tile) { var tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord); @@ -434,8 +312,8 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = framebufferExtentSize.width - 1; var ty = 2 * (tileExtent.minY - framebufferExtent.minY) / framebufferExtentSize.height - 1; - goog.vec.Vec4.setFromValues(uTileOffset, sx, sy, tx, ty); - gl.uniform4fv(this.locations_.uTileOffset, uTileOffset); + goog.vec.Vec4.setFromValues(u_tileOffset, sx, sy, tx, ty); + gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset); mapRenderer.bindTileTexture(tile, goog.webgl.LINEAR, goog.webgl.LINEAR); gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4); }, this); @@ -469,21 +347,22 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = tileSource.useLowResolutionTiles(z, extent, tileGrid); this.scheduleExpireCache(frameState, tileSource); - goog.vec.Mat4.makeIdentity(this.texCoordMatrix_); - goog.vec.Mat4.translate(this.texCoordMatrix_, + var texCoordMatrix = this.texCoordMatrix; + goog.vec.Mat4.makeIdentity(texCoordMatrix); + goog.vec.Mat4.translate(texCoordMatrix, (center.x - framebufferExtent.minX) / (framebufferExtent.maxX - framebufferExtent.minX), (center.y - framebufferExtent.minY) / (framebufferExtent.maxY - framebufferExtent.minY), 0); - goog.vec.Mat4.rotateZ(this.texCoordMatrix_, view2DState.rotation); - goog.vec.Mat4.scale(this.texCoordMatrix_, + goog.vec.Mat4.rotateZ(texCoordMatrix, view2DState.rotation); + goog.vec.Mat4.scale(texCoordMatrix, frameState.size.width * view2DState.resolution / (framebufferExtent.maxX - framebufferExtent.minX), frameState.size.height * view2DState.resolution / (framebufferExtent.maxY - framebufferExtent.minY), 1); - goog.vec.Mat4.translate(this.texCoordMatrix_, + goog.vec.Mat4.translate(texCoordMatrix, -0.5, -0.5, 0); diff --git a/src/ol/structs/buffer.js b/src/ol/structs/buffer.js new file mode 100644 index 0000000000..611c2e9e11 --- /dev/null +++ b/src/ol/structs/buffer.js @@ -0,0 +1,191 @@ +goog.provide('ol.structs.Buffer'); + +goog.require('goog.array'); +goog.require('goog.webgl'); +goog.require('ol.structs.IntegerSet'); + + +/** + * @enum {number} + */ +ol.structs.BufferUsage = { + STATIC_DRAW: goog.webgl.STATIC_DRAW, + STREAM_DRAW: goog.webgl.STREAM_DRAW, + DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW +}; + + +/** + * @define {boolean} Replace unused entries with NaNs. + */ +ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS = goog.DEBUG; + + + +/** + * @constructor + * @param {Array.=} opt_arr Array. + * @param {number=} opt_used Used. + * @param {number=} opt_usage Usage. + */ +ol.structs.Buffer = function(opt_arr, opt_used, opt_usage) { + + /** + * @private + * @type {Array.} + */ + this.arr_ = goog.isDef(opt_arr) ? opt_arr : []; + + /** + * @private + * @type {Array.} + */ + this.dirtySets_ = []; + + /** + * @private + * @type {ol.structs.IntegerSet} + */ + this.freeSet_ = new ol.structs.IntegerSet(); + + var used = goog.isDef(opt_used) ? opt_used : this.arr_.length; + if (used < this.arr_.length) { + this.freeSet_.addRange(used, this.arr_.length); + } + if (ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS) { + var arr = this.arr_; + var n = arr.length; + var i; + for (i = used; i < n; ++i) { + arr[i] = NaN; + } + } + + /** + * @private + * @type {number} + */ + this.usage_ = goog.isDef(opt_usage) ? + opt_usage : ol.structs.BufferUsage.STATIC_DRAW; + +}; + + +/** + * @param {Array.} values Values. + * @return {number} Offset. + */ +ol.structs.Buffer.prototype.add = function(values) { + var size = values.length; + goog.asserts.assert(size > 0); + var offset = this.freeSet_.findRange(size); + goog.asserts.assert(offset != -1); // FIXME + this.freeSet_.removeRange(offset, offset + size); + var i; + for (i = 0; i < size; ++i) { + this.arr_[offset + i] = values[i]; + } + for (i = 0; i < this.dirtySets_.length; ++i) { + this.dirtySets_[i].addRange(offset, offset + size); + } + return offset; +}; + + +/** + * @param {ol.structs.IntegerSet} dirtySet Dirty set. + */ +ol.structs.Buffer.prototype.addDirtySet = function(dirtySet) { + goog.asserts.assert(!goog.array.contains(this.dirtySets_, dirtySet)); + this.dirtySets_.push(dirtySet); +}; + + +/** + * @param {function(this: T, number, number)} f Callback. + * @param {T=} opt_obj The object to be used as the value of 'this' within f. + * @template T + */ +ol.structs.Buffer.prototype.forEachRange = function(f, opt_obj) { + if (this.arr_.length !== 0) { + this.freeSet_.forEachRangeInverted(0, this.arr_.length, f, opt_obj); + } +}; + + +/** + * @return {Array.} Array. + */ +ol.structs.Buffer.prototype.getArray = function() { + return this.arr_; +}; + + +/** + * @return {number} Count. + */ +ol.structs.Buffer.prototype.getCount = function() { + return this.arr_.length - this.freeSet_.getSize(); +}; + + +/** + * @return {ol.structs.IntegerSet} Free set. + */ +ol.structs.Buffer.prototype.getFreeSet = function() { + return this.freeSet_; +}; + + +/** + * @return {number} Usage. + */ +ol.structs.Buffer.prototype.getUsage = function() { + return this.usage_; +}; + + +/** + * @param {number} size Size. + * @param {number} offset Offset. + */ +ol.structs.Buffer.prototype.remove = function(size, offset) { + var i; + this.freeSet_.addRange(offset, offset + size); + for (i = 0; i < this.dirtySets_.length; ++i) { + this.dirtySets_[i].removeRange(offset, offset + size); + } + if (ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS) { + var arr = this.arr_; + for (i = 0; i < size; ++i) { + arr[offset + i] = NaN; + } + } +}; + + +/** + * @param {ol.structs.IntegerSet} dirtySet Dirty set. + */ +ol.structs.Buffer.prototype.removeDirtySet = function(dirtySet) { + var removed = goog.array.remove(this.dirtySets_, dirtySet); + goog.asserts.assert(removed); +}; + + +/** + * @param {Array.} values Values. + * @param {number} offset Offset. + */ +ol.structs.Buffer.prototype.set = function(values, offset) { + var arr = this.arr_; + var n = values.length; + goog.asserts.assert(0 <= offset && offset + n <= arr.length); + var i; + for (i = 0; i < n; ++i) { + arr[offset + i] = values[i]; + } + for (i = 0; i < this.dirtySets_.length; ++i) { + this.dirtySets_[i].addRange(offset, offset + n); + } +}; diff --git a/src/ol/structs/integerset.js b/src/ol/structs/integerset.js new file mode 100644 index 0000000000..17967c57bc --- /dev/null +++ b/src/ol/structs/integerset.js @@ -0,0 +1,330 @@ +goog.provide('ol.structs.IntegerSet'); + +goog.require('goog.asserts'); + + + +/** + * A set of integers represented as a set of integer ranges. + * This implementation is designed for the case when the number of distinct + * integer ranges is small. + * @constructor + * @param {Array.=} opt_arr Array. + */ +ol.structs.IntegerSet = function(opt_arr) { + + /** + * @private + * @type {Array.} + */ + this.arr_ = goog.isDef(opt_arr) ? opt_arr : []; + + if (goog.DEBUG) { + this.assertValid(); + } + +}; + + +/** + * @param {number} addStart Start. + * @param {number} addStop Stop. + */ +ol.structs.IntegerSet.prototype.addRange = function(addStart, addStop) { + goog.asserts.assert(addStart <= addStop); + if (addStart == addStop) { + return; + } + var arr = this.arr_; + var n = arr.length; + var i; + for (i = 0; i < n; i += 2) { + if (addStart <= arr[i]) { + // FIXME check if splice is really needed + arr.splice(i, 0, addStart, addStop); + this.compactRanges_(); + return; + } + } + arr.push(addStart, addStop); + this.compactRanges_(); +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.IntegerSet.prototype.assertValid = function() { + var arr = this.arr_; + var n = arr.length; + goog.asserts.assert(n % 2 === 0); + var i; + for (i = 1; i < n; ++i) { + goog.asserts.assert(arr[i] > arr[i - 1]); + } +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.IntegerSet.prototype.clear = function() { + this.arr_.length = 0; +}; + + +/** + * @private + */ +ol.structs.IntegerSet.prototype.compactRanges_ = function() { + var arr = this.arr_; + var n = arr.length; + var rangeIndex = 0; + var lastRange = null; + var i; + for (i = 0; i < n; i += 2) { + if (arr[i] == arr[i + 1]) { + // pass + } else if (rangeIndex > 0 && + arr[rangeIndex - 2] <= arr[i] && + arr[i] <= arr[rangeIndex - 1]) { + arr[rangeIndex - 1] = Math.max(arr[rangeIndex - 1], arr[i + 1]); + } else { + arr[rangeIndex++] = arr[i]; + arr[rangeIndex++] = arr[i + 1]; + } + } + arr.length = rangeIndex; +}; + + +/** + * Finds the start of smallest range that is at least of length minSize, or -1 + * if no such range exists. + * @param {number} minSize Minimum size. + * @return {number} Index. + */ +ol.structs.IntegerSet.prototype.findRange = function(minSize) { + goog.asserts.assert(minSize > 0); + var arr = this.arr_; + var n = arr.length; + var bestIndex = -1; + var bestSize, i, size; + for (i = 0; i < n; i += 2) { + size = arr[i + 1] - arr[i]; + if (size == minSize) { + return arr[i]; + } else if (size > minSize && (bestIndex == -1 || size < bestSize)) { + bestIndex = arr[i]; + bestSize = size; + } + } + return bestIndex; +}; + + +/** + * Calls f with each integer range. + * @param {function(this: T, number, number)} f Callback. + * @param {T=} opt_obj The object to be used as the value of 'this' within f. + * @template T + */ +ol.structs.IntegerSet.prototype.forEachRange = function(f, opt_obj) { + var arr = this.arr_; + var n = arr.length; + var i; + for (i = 0; i < n; i += 2) { + f.call(opt_obj, arr[i], arr[i + 1]); + } +}; + + +/** + * Calls f with each integer range not in [start, stop) - 'this'. + * @param {number} start Start. + * @param {number} stop Stop. + * @param {function(this: T, number, number)} f Callback. + * @param {T=} opt_obj The object to be used as the value of 'this' within f. + * @template T + */ +ol.structs.IntegerSet.prototype.forEachRangeInverted = + function(start, stop, f, opt_obj) { + goog.asserts.assert(start < stop); + var arr = this.arr_; + var n = arr.length; + if (n === 0) { + f.call(opt_obj, start, stop); + } else { + if (start < arr[0]) { + f.call(opt_obj, start, arr[0]); + } + var i; + for (i = 1; i < n - 1; i += 2) { + f.call(opt_obj, arr[i], arr[i + 1]); + } + if (arr[n - 1] < stop) { + f.call(opt_obj, arr[n - 1], stop); + } + } +}; + + +/** + * @return {Array.} Array. + */ +ol.structs.IntegerSet.prototype.getArray = function() { + return this.arr_; +}; + + +/** + * Returns the first element in the set, or -1 if the set is empty. + * @return {number} Start. + */ +ol.structs.IntegerSet.prototype.getFirst = function() { + return this.arr_.length === 0 ? -1 : this.arr_[0]; +}; + + +/** + * Returns the first integer after the last element in the set, or -1 if the + * set is empty. + * @return {number} Last. + */ +ol.structs.IntegerSet.prototype.getLast = function() { + var n = this.arr_.length; + return n === 0 ? -1 : this.arr_[n - 1]; +}; + + +/** + * Returns the number of integers in the set. + * @return {number} Size. + */ +ol.structs.IntegerSet.prototype.getSize = function() { + var arr = this.arr_; + var n = arr.length; + var size = 0; + var i; + for (i = 0; i < n; i += 2) { + size += arr[i + 1] - arr[i]; + } + return size; +}; + + +/** + * @param {number} start Start. + * @param {number} stop Stop. + * @return {boolean} Intersects range. + */ +ol.structs.IntegerSet.prototype.intersectsRange = function(start, stop) { + goog.asserts.assert(start <= stop); + if (start == stop) { + return false; + } else { + var arr = this.arr_; + var n = arr.length; + var i = 0; + for (i = 0; i < n; i += 2) { + if (arr[i] <= start && start < arr[i + 1] || + arr[i] < stop && stop - 1 < arr[i + 1] || + start < arr[i] && arr[i + 1] <= stop) { + return true; + } + } + return false; + } +}; + + +/** + * @return {boolean} Is empty. + */ +ol.structs.IntegerSet.prototype.isEmpty = function() { + return this.arr_.length === 0; +}; + + +/** + * @return {Array.} Array. + */ +ol.structs.IntegerSet.prototype.pack = function() { + return this.arr_; +}; + + +/** + * @param {number} removeStart Start. + * @param {number} removeStop Stop. + */ +ol.structs.IntegerSet.prototype.removeRange = + function(removeStart, removeStop) { + // FIXME this could be more efficient + goog.asserts.assert(removeStart <= removeStop); + var arr = this.arr_; + var n = arr.length; + var i; + for (i = 0; i < n; i += 2) { + if (removeStop < arr[i] || arr[i + 1] < removeStart) { + continue; + } else if (arr[i] > removeStop) { + break; + } + if (removeStart < arr[i]) { + if (removeStop == arr[i]) { + break; + } else if (removeStop < arr[i + 1]) { + arr[i] = Math.max(arr[i], removeStop); + break; + } else { + arr.splice(i, 2); + i -= 2; + n -= 2; + } + } else if (removeStart == arr[i]) { + if (removeStop < arr[i + 1]) { + arr[i] = removeStop; + break; + } else if (removeStop == arr[i + 1]) { + arr.splice(i, 2); + break; + } else { + arr.splice(i, 2); + i -= 2; + n -= 2; + } + } else { + if (removeStop < arr[i + 1]) { + arr.splice(i, 2, arr[i], removeStart, removeStop, arr[i + 1]); + break; + } else if (removeStop == arr[i + 1]) { + arr[i + 1] = removeStart; + break; + } else { + arr[i + 1] = removeStart; + } + } + } + this.compactRanges_(); +}; + + +if (goog.DEBUG) { + + /** + * @return {string} String. + */ + ol.structs.IntegerSet.prototype.toString = function() { + var arr = this.arr_; + var n = arr.length; + var result = new Array(n / 2); + var resultIndex = 0; + var i; + for (i = 0; i < n; i += 2) { + result[resultIndex++] = arr[i] + '-' + arr[i + 1]; + } + return result.join(', '); + }; + +} diff --git a/test/expect-0.2.0-ol3/expect.js b/test/expect-0.2.0-ol3/expect.js index 2fe81d4575..6c2ec48248 100644 --- a/test/expect-0.2.0-ol3/expect.js +++ b/test/expect-0.2.0-ol3/expect.js @@ -522,7 +522,8 @@ if (equal) { var j = 0; for (j = 0; j < other.length; ++j) { - if (this.obj[j] !== other[j]) { + if (this.obj[j] !== other[j] && + !(isNaN(this.obj[j]) && isNaN(other[j]))) { equal = false; break; } diff --git a/test/spec/ol/math.test.js b/test/spec/ol/math.test.js index e8fda89ac5..a73247469a 100644 --- a/test/spec/ol/math.test.js +++ b/test/spec/ol/math.test.js @@ -64,6 +64,46 @@ describe('ol.math.csch', function() { }); +describe('ol.math.roundUpToPowerOfTwo', function() { + + it('raises an exception when x is negative', function() { + expect(function() { + ol.math.roundUpToPowerOfTwo(-1); + }).to.throwException(); + }); + + it('raises an exception when x is zero', function() { + expect(function() { + ol.math.roundUpToPowerOfTwo(0); + }).to.throwException(); + }); + + it('returns the expected value for simple powers of two', function() { + expect(ol.math.roundUpToPowerOfTwo(1)).to.be(1); + expect(ol.math.roundUpToPowerOfTwo(2)).to.be(2); + expect(ol.math.roundUpToPowerOfTwo(4)).to.be(4); + expect(ol.math.roundUpToPowerOfTwo(8)).to.be(8); + expect(ol.math.roundUpToPowerOfTwo(16)).to.be(16); + expect(ol.math.roundUpToPowerOfTwo(32)).to.be(32); + expect(ol.math.roundUpToPowerOfTwo(64)).to.be(64); + expect(ol.math.roundUpToPowerOfTwo(128)).to.be(128); + expect(ol.math.roundUpToPowerOfTwo(256)).to.be(256); + }); + + it('returns the expected value for simple powers of ten', function() { + expect(ol.math.roundUpToPowerOfTwo(1)).to.be(1); + expect(ol.math.roundUpToPowerOfTwo(10)).to.be(16); + expect(ol.math.roundUpToPowerOfTwo(100)).to.be(128); + expect(ol.math.roundUpToPowerOfTwo(1000)).to.be(1024); + expect(ol.math.roundUpToPowerOfTwo(10000)).to.be(16384); + expect(ol.math.roundUpToPowerOfTwo(100000)).to.be(131072); + expect(ol.math.roundUpToPowerOfTwo(1000000)).to.be(1048576); + expect(ol.math.roundUpToPowerOfTwo(10000000)).to.be(16777216); + }); + +}); + + describe('ol.math.sech', function() { it('returns the correct value at -Infinity', function() { diff --git a/test/spec/ol/structs/buffer.test.js b/test/spec/ol/structs/buffer.test.js new file mode 100644 index 0000000000..1b6b019038 --- /dev/null +++ b/test/spec/ol/structs/buffer.test.js @@ -0,0 +1,262 @@ +goog.provide('ol.test.structs.Buffer'); + + +describe('ol.structs.Buffer', function() { + + describe('constructor', function() { + + describe('without an argument', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer(); + }); + + it('constructs an empty instance', function() { + expect(b.getArray()).to.be.empty(); + expect(b.getCount()).to.be(0); + }); + + }); + + describe('with a single array argument', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer([0, 1, 2, 3]); + }); + + it('constructs a populated instance', function() { + expect(b.getArray()).to.equalArray([0, 1, 2, 3]); + }); + + }); + + }); + + describe('with an empty instance', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer(); + }); + + describe('forEachRange', function() { + + it('does not call the callback', function() { + var callback = sinon.spy(); + b.forEachRange(callback); + expect(callback).not.to.be.called(); + }); + + }); + + describe('getArray', function() { + + it('returns an empty array', function() { + expect(b.getArray()).to.be.empty(); + }); + + }); + + describe('getCount', function() { + + it('returns 0', function() { + expect(b.getCount()).to.be(0); + }); + + }); + + }); + + describe('with an empty instance with spare capacity', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer(new Array(4), 0); + }); + + describe('add', function() { + + it('allows elements to be added', function() { + expect(b.add([0, 1, 2, 3])).to.be(0); + expect(b.getArray()).to.equalArray([0, 1, 2, 3]); + }); + + }); + + describe('forEachRange', function() { + + it('does not call the callback', function() { + var callback = sinon.spy(); + b.forEachRange(callback); + expect(callback).not.to.be.called(); + }); + + }); + + describe('getCount', function() { + + it('returns 0', function() { + expect(b.getCount()).to.be(0); + }); + + }); + + }); + + describe('with an instance with no spare capacity', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer([0, 1, 2, 3]); + }); + + describe('add', function() { + + it('throws an exception', function() { + expect(function() { + b.add([4, 5]); + }).to.throwException(); + }); + + }); + + describe('forEachRange', function() { + + it('calls the callback', function() { + var callback = sinon.spy(); + b.forEachRange(callback); + expect(callback.calledOnce).to.be(true); + expect(callback.args[0]).to.equalArray([0, 4]); + }); + + }); + + describe('getCount', function() { + + it('returns the expected value', function() { + expect(b.getCount()).to.be(4); + }); + + }); + + describe('remove', function() { + + it('allows items to be removes', function() { + expect(function() { + b.remove(4, 2); + }).to.not.throwException(); + }); + + }); + + describe('set', function() { + + it('updates the items', function() { + b.set([5, 6], 2); + expect(b.getArray()).to.equalArray([0, 1, 5, 6]); + }); + + it('marks the set items as dirty', function() { + var dirtySet = new ol.structs.IntegerSet(); + b.addDirtySet(dirtySet); + expect(dirtySet.isEmpty()).to.be(true); + b.set([5, 6], 2); + expect(dirtySet.isEmpty()).to.be(false); + expect(dirtySet.getArray()).to.equalArray([2, 4]); + }); + + }); + + }); + + describe('with an instance with spare capacity', function() { + + var b; + beforeEach(function() { + var arr = [0, 1, 2, 3]; + arr.length = 8; + b = new ol.structs.Buffer(arr, 4); + }); + + describe('add', function() { + + it('allows more items to be added', function() { + expect(b.add([4, 5, 6, 7])).to.be(4); + expect(b.getArray()).to.equalArray([0, 1, 2, 3, 4, 5, 6, 7]); + }); + + }); + + describe('forEachRange', function() { + + it('calls the callback with the expected values', function() { + var callback = sinon.spy(); + b.forEachRange(callback); + expect(callback.calledOnce).to.be(true); + expect(callback.args[0]).to.equalArray([0, 4]); + }); + + }); + + describe('getCount', function() { + + it('returns the expected value', function() { + expect(b.getCount()).to.be(4); + }); + + }); + + describe('getFreeSet', function() { + + it('returns the expected set', function() { + var freeSet = b.getFreeSet(); + expect(freeSet.isEmpty()).to.be(false); + expect(freeSet.getArray()).to.equalArray([4, 8]); + }); + + }); + + }); + + describe('usage tests', function() { + + it('allows multiple adds and removes', function() { + var b = new ol.structs.Buffer(new Array(8), 0); + expect(b.add([0, 1])).to.be(0); + expect(b.getArray()).to.equalArray([0, 1, NaN, NaN, NaN, NaN, NaN, NaN]); + expect(b.getCount()).to.be(2); + expect(b.add([2, 3, 4, 5])).to.be(2); + expect(b.getArray()).to.equalArray([0, 1, 2, 3, 4, 5, NaN, NaN]); + expect(b.getCount()).to.be(6); + expect(b.add([6, 7])).to.be(6); + expect(b.getArray()).to.equalArray([0, 1, 2, 3, 4, 5, 6, 7]); + expect(b.getCount()).to.be(8); + b.remove(2, 2); + expect(b.getArray()).to.equalArray([0, 1, NaN, NaN, 4, 5, 6, 7]); + expect(b.getCount()).to.be(6); + expect(b.add([8, 9])).to.be(2); + expect(b.getArray()).to.equalArray([0, 1, 8, 9, 4, 5, 6, 7]); + expect(b.getCount()).to.be(8); + b.remove(1, 1); + expect(b.getArray()).to.equalArray([0, NaN, 8, 9, 4, 5, 6, 7]); + expect(b.getCount()).to.be(7); + b.remove(4, 4); + expect(b.getArray()).to.equalArray([0, NaN, 8, 9, NaN, NaN, NaN, NaN]); + expect(b.getCount()).to.be(3); + expect(b.add([10, 11, 12])).to.be(4); + expect(b.getArray()).to.equalArray([0, NaN, 8, 9, 10, 11, 12, NaN]); + expect(b.getCount()).to.be(6); + expect(b.add([13])).to.be(1); + expect(b.getArray()).to.equalArray([0, 13, 8, 9, 10, 11, 12, NaN]); + expect(b.getCount()).to.be(7); + }); + + }); + +}); + + +goog.require('ol.structs.Buffer'); +goog.require('ol.structs.IntegerSet'); diff --git a/test/spec/ol/structs/integerset.test.js b/test/spec/ol/structs/integerset.test.js new file mode 100644 index 0000000000..48a8328dc7 --- /dev/null +++ b/test/spec/ol/structs/integerset.test.js @@ -0,0 +1,619 @@ +goog.provide('ol.test.structs.IntegerSet'); + + +describe('ol.structs.IntegerSet', function() { + + describe('constructor', function() { + + describe('without an argument', function() { + + it('constructs an empty instance', function() { + var is = new ol.structs.IntegerSet(); + expect(is).to.be.an(ol.structs.IntegerSet); + expect(is.getArray()).to.be.empty(); + }); + + }); + + describe('with an argument', function() { + + it('constructs with a valid array', function() { + var is = new ol.structs.IntegerSet([0, 2, 4, 6]); + expect(is).to.be.an(ol.structs.IntegerSet); + expect(is.getArray()).to.equalArray([0, 2, 4, 6]); + }); + + it('throws an exception with an odd number of elements', function() { + expect(function() { + var is = new ol.structs.IntegerSet([0, 2, 4]); + }).to.throwException(); + }); + + it('throws an exception with out-of-order elements', function() { + expect(function() { + var is = new ol.structs.IntegerSet([0, 2, 2, 4]); + }).to.throwException(); + }); + + }); + + }); + + describe('with an empty instance', function() { + + var is; + beforeEach(function() { + is = new ol.structs.IntegerSet(); + }); + + describe('addRange', function() { + + it('creates a new element', function() { + is.addRange(0, 2); + expect(is.getArray()).to.equalArray([0, 2]); + }); + + }); + + describe('findRange', function() { + + it('returns -1', function() { + expect(is.findRange(2)).to.be(-1); + }); + + }); + + describe('forEachRange', function() { + + it('does not call the callback', function() { + var callback = sinon.spy(); + is.forEachRange(callback); + expect(callback).to.not.be.called(); + }); + + }); + + describe('forEachRangeInverted', function() { + + it('does call the callback', function() { + var callback = sinon.spy(); + is.forEachRangeInverted(0, 8, callback); + expect(callback.calledOnce).to.be(true); + expect(callback.args[0]).to.equalArray([0, 8]); + }); + + }); + + describe('getFirst', function() { + + it('returns -1', function() { + expect(is.getFirst()).to.be(-1); + }); + + }); + + describe('getLast', function() { + + it('returns -1', function() { + expect(is.getLast()).to.be(-1); + }); + + }); + + describe('getSize', function() { + + it('returns 0', function() { + expect(is.getSize()).to.be(0); + }); + + }); + + describe('intersectsRange', function() { + + it('returns false', function() { + expect(is.intersectsRange(0, 0)).to.be(false); + }); + + }); + + describe('isEmpty', function() { + + it('returns true', function() { + expect(is.isEmpty()).to.be(true); + }); + + }); + + describe('toString', function() { + + it('returns an empty string', function() { + expect(is.toString()).to.be.empty(); + }); + + }); + + }); + + describe('with a populated instance', function() { + + var is; + beforeEach(function() { + is = new ol.structs.IntegerSet([4, 6, 8, 10, 12, 14]); + }); + + describe('addRange', function() { + + it('inserts before the first element', function() { + is.addRange(0, 2); + expect(is.getArray()).to.equalArray([0, 2, 4, 6, 8, 10, 12, 14]); + }); + + it('extends the first element to the left', function() { + is.addRange(0, 4); + expect(is.getArray()).to.equalArray([0, 6, 8, 10, 12, 14]); + }); + + it('extends the first element to the right', function() { + is.addRange(6, 7); + expect(is.getArray()).to.equalArray([4, 7, 8, 10, 12, 14]); + }); + + it('merges the first two elements', function() { + is.addRange(6, 8); + expect(is.getArray()).to.equalArray([4, 10, 12, 14]); + }); + + it('extends middle elements to the left', function() { + is.addRange(7, 8); + expect(is.getArray()).to.equalArray([4, 6, 7, 10, 12, 14]); + }); + + it('extends middle elements to the right', function() { + is.addRange(10, 11); + expect(is.getArray()).to.equalArray([4, 6, 8, 11, 12, 14]); + }); + + it('merges the last two elements', function() { + is.addRange(10, 12); + expect(is.getArray()).to.equalArray([4, 6, 8, 14]); + }); + + it('extends the last element to the left', function() { + is.addRange(11, 12); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 11, 14]); + }); + + it('extends the last element to the right', function() { + is.addRange(14, 15); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 12, 15]); + }); + + it('inserts after the last element', function() { + is.addRange(16, 18); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 12, 14, 16, 18]); + }); + + }); + + describe('clear', function() { + + it('clears the instance', function() { + is.clear(); + expect(is.getArray()).to.be.empty(); + }); + + }); + + describe('findRange', function() { + + it('throws an exception when passed a negative size', function() { + expect(function() { + is.findRange(-1); + }).to.throwException(); + }); + + it('throws an exception when passed a zero size', function() { + expect(function() { + is.findRange(0); + }).to.throwException(); + }); + + it('finds the first range of size 1', function() { + expect(is.findRange(1)).to.be(4); + }); + + it('finds the first range of size 2', function() { + expect(is.findRange(2)).to.be(4); + }); + + it('returns -1 when no range can be found', function() { + expect(is.findRange(3)).to.be(-1); + }); + + }); + + describe('forEachRange', function() { + + it('calls the callback', function() { + var callback = sinon.spy(); + is.forEachRange(callback); + expect(callback).to.be.called(); + expect(callback.calledThrice).to.be(true); + expect(callback.args[0]).to.equalArray([4, 6]); + expect(callback.args[1]).to.equalArray([8, 10]); + expect(callback.args[2]).to.equalArray([12, 14]); + }); + + }); + + describe('forEachRangeInverted', function() { + + it('does call the callback', function() { + var callback = sinon.spy(); + is.forEachRangeInverted(0, 16, callback); + expect(callback.callCount).to.be(4); + expect(callback.args[0]).to.equalArray([0, 4]); + expect(callback.args[1]).to.equalArray([6, 8]); + expect(callback.args[2]).to.equalArray([10, 12]); + expect(callback.args[3]).to.equalArray([14, 16]); + }); + + }); + + + describe('getFirst', function() { + + it('returns the expected value', function() { + expect(is.getFirst()).to.be(4); + }); + + }); + + describe('getLast', function() { + + it('returns the expected value', function() { + expect(is.getLast()).to.be(14); + }); + + }); + + describe('getSize', function() { + + it('returns the expected value', function() { + expect(is.getSize()).to.be(6); + }); + + }); + + describe('intersectsRange', function() { + + it('returns the expected value for small ranges', function() { + expect(is.intersectsRange(1, 3)).to.be(false); + expect(is.intersectsRange(2, 4)).to.be(false); + expect(is.intersectsRange(3, 5)).to.be(true); + expect(is.intersectsRange(4, 6)).to.be(true); + expect(is.intersectsRange(5, 7)).to.be(true); + expect(is.intersectsRange(6, 8)).to.be(false); + expect(is.intersectsRange(7, 9)).to.be(true); + expect(is.intersectsRange(8, 10)).to.be(true); + expect(is.intersectsRange(9, 11)).to.be(true); + expect(is.intersectsRange(10, 12)).to.be(false); + expect(is.intersectsRange(11, 13)).to.be(true); + expect(is.intersectsRange(12, 14)).to.be(true); + expect(is.intersectsRange(13, 15)).to.be(true); + expect(is.intersectsRange(14, 16)).to.be(false); + expect(is.intersectsRange(15, 17)).to.be(false); + }); + + it('returns the expected value for large ranges', function() { + expect(is.intersectsRange(-3, 1)).to.be(false); + expect(is.intersectsRange(1, 5)).to.be(true); + expect(is.intersectsRange(1, 9)).to.be(true); + expect(is.intersectsRange(1, 13)).to.be(true); + expect(is.intersectsRange(1, 17)).to.be(true); + expect(is.intersectsRange(5, 9)).to.be(true); + expect(is.intersectsRange(5, 13)).to.be(true); + expect(is.intersectsRange(5, 17)).to.be(true); + expect(is.intersectsRange(9, 13)).to.be(true); + expect(is.intersectsRange(9, 17)).to.be(true); + expect(is.intersectsRange(13, 17)).to.be(true); + expect(is.intersectsRange(17, 21)).to.be(false); + }); + + }); + + describe('isEmpty', function() { + + it('returns false', function() { + expect(is.isEmpty()).to.be(false); + }); + + }); + + describe('removeRange', function() { + + it('removes the first part of the first element', function() { + is.removeRange(4, 5); + expect(is.getArray()).to.equalArray([5, 6, 8, 10, 12, 14]); + }); + + it('removes the last part of the first element', function() { + is.removeRange(5, 6); + expect(is.getArray()).to.equalArray([4, 5, 8, 10, 12, 14]); + }); + + it('removes the first element', function() { + is.removeRange(4, 6); + expect(is.getArray()).to.equalArray([8, 10, 12, 14]); + }); + + it('removes the first part of a middle element', function() { + is.removeRange(8, 9); + expect(is.getArray()).to.equalArray([4, 6, 9, 10, 12, 14]); + }); + + it('removes the last part of a middle element', function() { + is.removeRange(9, 10); + expect(is.getArray()).to.equalArray([4, 6, 8, 9, 12, 14]); + }); + + it('removes a middle element', function() { + is.removeRange(8, 10); + expect(is.getArray()).to.equalArray([4, 6, 12, 14]); + }); + + it('removes the first part of the last element', function() { + is.removeRange(12, 13); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 13, 14]); + }); + + it('removes the last part of the last element', function() { + is.removeRange(13, 14); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 12, 13]); + }); + + it('removes the last element', function() { + is.removeRange(12, 14); + expect(is.getArray()).to.equalArray([4, 6, 8, 10]); + }); + + it('can remove multiple ranges near the start', function() { + is.removeRange(3, 11); + expect(is.getArray()).to.equalArray([12, 14]); + }); + + it('can remove multiple ranges near the start', function() { + is.removeRange(7, 15); + expect(is.getArray()).to.equalArray([4, 6]); + }); + + it('throws an exception when passed an invalid range', function() { + expect(function() { + is.removeRange(2, 0); + }).to.throwException(); + }); + + }); + + describe('toString', function() { + + it('returns the expected value', function() { + expect(is.toString()).to.be('4-6, 8-10, 12-14'); + }); + }); + + }); + + describe('with fragmentation', function() { + + var is; + beforeEach(function() { + is = new ol.structs.IntegerSet([0, 1, 2, 4, 5, 8, 9, 12, 13, 15, 16, 17]); + }); + + describe('findRange', function() { + + it('finds the first range of size 1', function() { + expect(is.findRange(1)).to.be(0); + }); + + it('finds the first range of size 2', function() { + expect(is.findRange(2)).to.be(2); + }); + + it('finds the first range of size 3', function() { + expect(is.findRange(3)).to.be(5); + }); + + it('returns -1 when no range can be found', function() { + expect(is.findRange(4)).to.be(-1); + }); + + }); + + describe('getFirst', function() { + + it('returns the expected value', function() { + expect(is.getFirst()).to.be(0); + }); + + }); + + describe('getLast', function() { + + it('returns the expected value', function() { + expect(is.getLast()).to.be(17); + }); + + }); + + describe('getSize', function() { + + it('returns the expected value', function() { + expect(is.getSize()).to.be(12); + }); + + }); + + describe('removeRange', function() { + + it('removing an empty range has no effect', function() { + is.removeRange(0, 0); + expect(is.getArray()).to.equalArray( + [0, 1, 2, 4, 5, 8, 9, 12, 13, 15, 16, 17]); + }); + + it('can remove elements from the middle of range', function() { + is.removeRange(6, 7); + expect(is.getArray()).to.equalArray( + [0, 1, 2, 4, 5, 6, 7, 8, 9, 12, 13, 15, 16, 17]); + }); + + it('can remove multiple ranges', function() { + is.removeRange(2, 12); + expect(is.getArray()).to.equalArray([0, 1, 13, 15, 16, 17]); + }); + + it('can remove multiple ranges and reduce others', function() { + is.removeRange(0, 10); + expect(is.getArray()).to.equalArray([10, 12, 13, 15, 16, 17]); + }); + + it('can remove all ranges', function() { + is.removeRange(0, 18); + expect(is.getArray()).to.equalArray([]); + }); + + }); + + describe('toString', function() { + + it('returns the expected value', function() { + expect(is.toString()).to.be('0-1, 2-4, 5-8, 9-12, 13-15, 16-17'); + }); + + }); + + }); + + describe('compared to a slow reference implementation', function() { + + var SimpleIntegerSet = function() { + this.integers_ = {}; + }; + + SimpleIntegerSet.prototype.addRange = function(addStart, addStop) { + var i; + for (i = addStart; i < addStop; ++i) { + this.integers_[i.toString()] = true; + } + }; + + SimpleIntegerSet.prototype.clear = function() { + this.integers_ = {}; + }; + + SimpleIntegerSet.prototype.getArray = function() { + var integers = goog.array.map( + goog.object.getKeys(this.integers_), Number); + goog.array.sort(integers); + var arr = []; + var start = -1, stop; + var i; + for (i = 0; i < integers.length; ++i) { + if (start == -1) { + start = stop = integers[i]; + } else if (integers[i] == stop + 1) { + ++stop; + } else { + arr.push(start, stop + 1); + start = stop = integers[i]; + } + } + if (start != -1) { + arr.push(start, stop + 1); + } + return arr; + }; + + SimpleIntegerSet.prototype.removeRange = function(removeStart, removeStop) { + var i; + for (i = removeStart; i < removeStop; ++i) { + delete this.integers_[i.toString()]; + } + }; + + var is, sis; + beforeEach(function() { + is = new ol.structs.IntegerSet(); + sis = new SimpleIntegerSet(); + }); + + it('behaves identically with random adds', function() { + var addStart, addStop, i; + for (i = 0; i < 64; ++i) { + addStart = goog.math.randomInt(128); + addStop = addStart + goog.math.randomInt(16); + is.addRange(addStart, addStop); + sis.addRange(addStart, addStop); + expect(is.getArray()).to.equalArray(sis.getArray()); + } + }); + + it('behaves identically with random removes', function() { + is.addRange(0, 128); + sis.addRange(0, 128); + var i, removeStart, removeStop; + for (i = 0; i < 64; ++i) { + removeStart = goog.math.randomInt(128); + removeStop = removeStart + goog.math.randomInt(16); + is.removeRange(removeStart, removeStop); + sis.removeRange(removeStart, removeStop); + expect(is.getArray()).to.equalArray(sis.getArray()); + } + }); + + it('behaves identically with random adds and removes', function() { + var i, start, stop; + for (i = 0; i < 64; ++i) { + start = goog.math.randomInt(128); + stop = start + goog.math.randomInt(16); + if (Math.random() < 0.5) { + is.addRange(start, stop); + sis.addRange(start, stop); + } else { + is.removeRange(start, stop); + sis.removeRange(start, stop); + } + expect(is.getArray()).to.equalArray(sis.getArray()); + } + }); + + it('behaves identically with random adds, removes, and clears', function() { + var i, p, start, stop; + for (i = 0; i < 64; ++i) { + start = goog.math.randomInt(128); + stop = start + goog.math.randomInt(16); + p = Math.random(); + if (p < 0.45) { + is.addRange(start, stop); + sis.addRange(start, stop); + } else if (p < 0.9) { + is.removeRange(start, stop); + sis.removeRange(start, stop); + } else { + is.clear(); + sis.clear(); + } + expect(is.getArray()).to.equalArray(sis.getArray()); + } + }); + + }); + +}); + + +goog.require('goog.array'); +goog.require('goog.object'); +goog.require('ol.structs.IntegerSet');