Merge pull request #2967 from camptocamp/webgl-point

Add support for drawing points with WebGL
This commit is contained in:
Éric Lemoine
2014-12-04 13:11:31 +01:00
46 changed files with 4403 additions and 1741 deletions

View File

@@ -119,21 +119,32 @@ ol.has.MSPOINTER = !!(goog.global.navigator.msPointerEnabled);
* @type {boolean}
* @api stable
*/
ol.has.WEBGL = ol.ENABLE_WEBGL && (
/**
* @return {boolean} WebGL supported.
*/
function() {
if (!('WebGLRenderingContext' in goog.global)) {
return false;
}
ol.has.WEBGL;
(function() {
if (ol.ENABLE_WEBGL) {
var hasWebGL = false;
var textureSize;
var /** @type {Array.<string>} */ extensions = [];
if ('WebGLRenderingContext' in goog.global) {
try {
var canvas = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
return !goog.isNull(ol.webgl.getContext(canvas, {
var gl = ol.webgl.getContext(canvas, {
failIfMajorPerformanceCaveat: true
}));
} catch (e) {
return false;
}
})();
});
if (!goog.isNull(gl)) {
hasWebGL = true;
textureSize = /** @type {number} */
(gl.getParameter(gl.MAX_TEXTURE_SIZE));
extensions = gl.getSupportedExtensions();
}
} catch (e) {}
}
ol.has.WEBGL = hasWebGL;
ol.WEBGL_EXTENSIONS = extensions;
ol.WEBGL_MAX_TEXTURE_SIZE = textureSize;
}
})();

View File

@@ -152,6 +152,13 @@ ol.ENABLE_WEBGL = true;
ol.LEGACY_IE_SUPPORT = false;
/**
* @define {number} The size in pixels of the first atlas image. Default is
* `256`.
*/
ol.INITIAL_ATLAS_SIZE = 256;
/**
* The page is loaded using HTTPS.
* @const
@@ -175,6 +182,14 @@ ol.IS_LEGACY_IE = goog.userAgent.IE &&
ol.KEYBOARD_PAN_DURATION = 100;
/**
* @define {number} The maximum size in pixels of atlas images. Default is
* `-1`, meaning it is not used (and `ol.ol.WEBGL_MAX_TEXTURE_SIZE` is
* used instead).
*/
ol.MAX_ATLAS_SIZE = -1;
/**
* @define {number} Maximum mouse wheel delta.
*/
@@ -219,6 +234,24 @@ ol.SIMPLIFY_TOLERANCE = 0.5;
ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024;
/**
* The maximum supported WebGL texture size in pixels. If WebGL is not
* supported, the value is set to `undefined`.
* @const
* @type {number|undefined}
* @api
*/
ol.WEBGL_MAX_TEXTURE_SIZE; // value is set in `ol.has`
/**
* List of supported WebGL extensions.
* @const
* @type {Array.<string>}
*/
ol.WEBGL_EXTENSIONS; // value is set in `ol.has`
/**
* @define {number} Zoom slider animation duration.
*/

View File

@@ -2011,7 +2011,7 @@ ol.render.canvas.ReplayGroup.prototype.forEachGeometryAtPixel = function(
/**
* @inheritDoc
* FIXME empty description for jsdoc
*/
ol.render.canvas.ReplayGroup.prototype.finish = function() {
var zKey;

View File

@@ -35,13 +35,6 @@ ol.render.IReplayGroup = function() {
};
/**
* FIXME empty description for jsdoc
*/
ol.render.IReplayGroup.prototype.finish = function() {
};
/**
* @param {number|undefined} zIndex Z index.
* @param {ol.render.ReplayType} replayType Replay type.

View File

@@ -5,8 +5,8 @@ goog.provide('ol.render.IVectorContext');
/**
* VectorContext interface. Currently implemented by
* {@link ol.render.canvas.Immediate}
* VectorContext interface. Implemented by
* {@link ol.render.canvas.Immediate} and {@link ol.render.webgl.Immediate}.
* @interface
*/
ol.render.IVectorContext = function() {
@@ -15,7 +15,7 @@ ol.render.IVectorContext = function() {
/**
* @param {number} zIndex Z index.
* @param {function(ol.render.canvas.Immediate)} callback Callback.
* @param {function(ol.render.IVectorContext)} callback Callback.
*/
ol.render.IVectorContext.prototype.drawAsync = function(zIndex, callback) {
};

View File

@@ -0,0 +1,46 @@
//! NAMESPACE=ol.render.webgl.imagereplay.shader.Color
//! CLASS=ol.render.webgl.imagereplay.shader.Color
//! COMMON
varying vec2 v_texCoord;
varying float v_opacity;
//! VERTEX
attribute vec2 a_position;
attribute vec2 a_texCoord;
attribute vec2 a_offsets;
attribute float a_opacity;
attribute float a_rotateWithView;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
uniform mat4 u_offsetRotateMatrix;
void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix;
if (a_rotateWithView == 1.0) {
offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
}
vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);
gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;
v_texCoord = a_texCoord;
v_opacity = a_opacity;
}
//! FRAGMENT
// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp
uniform mat4 u_colorMatrix;
uniform float u_opacity;
uniform sampler2D u_image;
void main(void) {
vec4 texColor = texture2D(u_image, v_texCoord);
float alpha = texColor.a * v_opacity * u_opacity;
if (alpha == 0.0) {
discard;
}
gl_FragColor.a = alpha;
gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;
}

View File

@@ -0,0 +1,153 @@
// This file is automatically generated, do not edit
goog.provide('ol.render.webgl.imagereplay.shader.Color');
goog.require('ol.webgl.shader');
/**
* @constructor
* @extends {ol.webgl.shader.Fragment}
* @struct
*/
ol.render.webgl.imagereplay.shader.ColorFragment = function() {
goog.base(this, ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE);
};
goog.inherits(ol.render.webgl.imagereplay.shader.ColorFragment, ol.webgl.shader.Fragment);
goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorFragment);
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\n// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp\nuniform mat4 u_colorMatrix;\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;\n}\n';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform mat4 k;uniform float l;uniform sampler2D m;void main(void){vec4 texColor=texture2D(m,a);float alpha=texColor.a*b*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;gl_FragColor.rgb=(k*vec4(texColor.rgb,1.)).rgb;}';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE = goog.DEBUG ?
ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE :
ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE;
/**
* @constructor
* @extends {ol.webgl.shader.Vertex}
* @struct
*/
ol.render.webgl.imagereplay.shader.ColorVertex = function() {
goog.base(this, ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE);
};
goog.inherits(ol.render.webgl.imagereplay.shader.ColorVertex, ol.webgl.shader.Vertex);
goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorVertex);
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorVertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE = goog.DEBUG ?
ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE :
ol.render.webgl.imagereplay.shader.ColorVertex.OPTIMIZED_SOURCE;
/**
* @constructor
* @param {WebGLRenderingContext} gl GL.
* @param {WebGLProgram} program Program.
* @struct
*/
ol.render.webgl.imagereplay.shader.Color.Locations = function(gl, program) {
/**
* @type {WebGLUniformLocation}
*/
this.u_colorMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_colorMatrix' : 'k');
/**
* @type {WebGLUniformLocation}
*/
this.u_image = gl.getUniformLocation(
program, goog.DEBUG ? 'u_image' : 'm');
/**
* @type {WebGLUniformLocation}
*/
this.u_offsetRotateMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j');
/**
* @type {WebGLUniformLocation}
*/
this.u_offsetScaleMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i');
/**
* @type {WebGLUniformLocation}
*/
this.u_opacity = gl.getUniformLocation(
program, goog.DEBUG ? 'u_opacity' : 'l');
/**
* @type {WebGLUniformLocation}
*/
this.u_projectionMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_projectionMatrix' : 'h');
/**
* @type {number}
*/
this.a_offsets = gl.getAttribLocation(
program, goog.DEBUG ? 'a_offsets' : 'e');
/**
* @type {number}
*/
this.a_opacity = gl.getAttribLocation(
program, goog.DEBUG ? 'a_opacity' : 'f');
/**
* @type {number}
*/
this.a_position = gl.getAttribLocation(
program, goog.DEBUG ? 'a_position' : 'c');
/**
* @type {number}
*/
this.a_rotateWithView = gl.getAttribLocation(
program, goog.DEBUG ? 'a_rotateWithView' : 'g');
/**
* @type {number}
*/
this.a_texCoord = gl.getAttribLocation(
program, goog.DEBUG ? 'a_texCoord' : 'd');
};

View File

@@ -0,0 +1,44 @@
//! NAMESPACE=ol.render.webgl.imagereplay.shader.Default
//! CLASS=ol.render.webgl.imagereplay.shader.Default
//! COMMON
varying vec2 v_texCoord;
varying float v_opacity;
//! VERTEX
attribute vec2 a_position;
attribute vec2 a_texCoord;
attribute vec2 a_offsets;
attribute float a_opacity;
attribute float a_rotateWithView;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
uniform mat4 u_offsetRotateMatrix;
void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix;
if (a_rotateWithView == 1.0) {
offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
}
vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);
gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;
v_texCoord = a_texCoord;
v_opacity = a_opacity;
}
//! FRAGMENT
uniform float u_opacity;
uniform sampler2D u_image;
void main(void) {
vec4 texColor = texture2D(u_image, v_texCoord);
gl_FragColor.rgb = texColor.rgb;
float alpha = texColor.a * v_opacity * u_opacity;
if (alpha == 0.0) {
discard;
}
gl_FragColor.a = alpha;
}

View File

@@ -0,0 +1,147 @@
// This file is automatically generated, do not edit
goog.provide('ol.render.webgl.imagereplay.shader.Default');
goog.require('ol.webgl.shader');
/**
* @constructor
* @extends {ol.webgl.shader.Fragment}
* @struct
*/
ol.render.webgl.imagereplay.shader.DefaultFragment = function() {
goog.base(this, ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE);
};
goog.inherits(ol.render.webgl.imagereplay.shader.DefaultFragment, ol.webgl.shader.Fragment);
goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultFragment);
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE = goog.DEBUG ?
ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE :
ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE;
/**
* @constructor
* @extends {ol.webgl.shader.Vertex}
* @struct
*/
ol.render.webgl.imagereplay.shader.DefaultVertex = function() {
goog.base(this, ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE);
};
goog.inherits(ol.render.webgl.imagereplay.shader.DefaultVertex, ol.webgl.shader.Vertex);
goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultVertex);
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE = goog.DEBUG ?
ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE :
ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE;
/**
* @constructor
* @param {WebGLRenderingContext} gl GL.
* @param {WebGLProgram} program Program.
* @struct
*/
ol.render.webgl.imagereplay.shader.Default.Locations = function(gl, program) {
/**
* @type {WebGLUniformLocation}
*/
this.u_image = gl.getUniformLocation(
program, goog.DEBUG ? 'u_image' : 'l');
/**
* @type {WebGLUniformLocation}
*/
this.u_offsetRotateMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j');
/**
* @type {WebGLUniformLocation}
*/
this.u_offsetScaleMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i');
/**
* @type {WebGLUniformLocation}
*/
this.u_opacity = gl.getUniformLocation(
program, goog.DEBUG ? 'u_opacity' : 'k');
/**
* @type {WebGLUniformLocation}
*/
this.u_projectionMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_projectionMatrix' : 'h');
/**
* @type {number}
*/
this.a_offsets = gl.getAttribLocation(
program, goog.DEBUG ? 'a_offsets' : 'e');
/**
* @type {number}
*/
this.a_opacity = gl.getAttribLocation(
program, goog.DEBUG ? 'a_opacity' : 'f');
/**
* @type {number}
*/
this.a_position = gl.getAttribLocation(
program, goog.DEBUG ? 'a_position' : 'c');
/**
* @type {number}
*/
this.a_rotateWithView = gl.getAttribLocation(
program, goog.DEBUG ? 'a_rotateWithView' : 'g');
/**
* @type {number}
*/
this.a_texCoord = gl.getAttribLocation(
program, goog.DEBUG ? 'a_texCoord' : 'd');
};

View File

@@ -1,4 +1,8 @@
goog.provide('ol.render.webgl.Immediate');
goog.require('goog.array');
goog.require('goog.object');
goog.require('ol.extent');
goog.require('ol.render.webgl.ReplayGroup');
@@ -6,22 +10,103 @@ goog.provide('ol.render.webgl.Immediate');
* @constructor
* @implements {ol.render.IVectorContext}
* @param {ol.webgl.Context} context Context.
* @param {ol.Coordinate} center Center.
* @param {number} resolution Resolution.
* @param {number} rotation Rotation.
* @param {ol.Size} size Size.
* @param {ol.Extent} extent Extent.
* @param {number} pixelRatio Pixel ratio.
* @struct
*/
ol.render.webgl.Immediate = function(context, pixelRatio) {
ol.render.webgl.Immediate = function(context,
center, resolution, rotation, size, extent, pixelRatio) {
/**
* @private
*/
this.context_ = context;
/**
* @private
*/
this.center_ = center;
/**
* @private
*/
this.extent_ = extent;
/**
* @private
*/
this.pixelRatio_ = pixelRatio;
/**
* @private
*/
this.size_ = size;
/**
* @private
*/
this.rotation_ = rotation;
/**
* @private
*/
this.resolution_ = resolution;
/**
* @private
* @type {ol.style.Image}
*/
this.imageStyle_ = null;
/**
* @private
* @type {Object.<string,
* Array.<function(ol.render.webgl.Immediate)>>}
*/
this.callbacksByZIndex_ = {};
};
/**
* @inheritDoc
* FIXME: empty description for jsdoc
*/
ol.render.webgl.Immediate.prototype.flush = function() {
/** @type {Array.<number>} */
var zs = goog.array.map(goog.object.getKeys(this.callbacksByZIndex_), Number);
goog.array.sort(zs);
var i, ii, callbacks, j, jj;
for (i = 0, ii = zs.length; i < ii; ++i) {
callbacks = this.callbacksByZIndex_[zs[i].toString()];
for (j = 0, jj = callbacks.length; j < jj; ++j) {
callbacks[j](this);
}
}
};
/**
* @param {number} zIndex Z index.
* @param {function(ol.render.webgl.Immediate)} callback Callback.
* @api
*/
ol.render.webgl.Immediate.prototype.drawAsync = function(zIndex, callback) {
var zIndexKey = zIndex.toString();
var callbacks = this.callbacksByZIndex_[zIndexKey];
if (goog.isDef(callbacks)) {
callbacks.push(callback);
} else {
this.callbacksByZIndex_[zIndexKey] = [callback];
}
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawCircleGeometry =
function(circleGeometry, data) {
@@ -30,29 +115,83 @@ ol.render.webgl.Immediate.prototype.drawCircleGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) {
var geometry = feature.getGeometry();
if (!goog.isDefAndNotNull(geometry) ||
!ol.extent.intersects(this.extent_, geometry.getExtent())) {
return;
}
var zIndex = style.getZIndex();
if (!goog.isDef(zIndex)) {
zIndex = 0;
}
this.drawAsync(zIndex, function(render) {
render.setFillStrokeStyle(style.getFill(), style.getStroke());
render.setImageStyle(style.getImage());
render.setTextStyle(style.getText());
var type = geometry.getType();
var renderGeometry = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_[type];
// Do not assert since all kinds of geometries are not handled yet.
// In spite, render what we support.
if (renderGeometry) {
renderGeometry.call(render, geometry, null);
}
});
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry =
function(geometryCollectionGeometry, data) {
var geometries = geometryCollectionGeometry.getGeometriesArray();
var renderers = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_;
var i, ii;
for (i = 0, ii = geometries.length; i < ii; ++i) {
var geometry = geometries[i];
var geometryRenderer = renderers[geometry.getType()];
// Do not assert since all kinds of geometries are not handled yet.
// In order to support hierarchies, delegate instead what we can to
// valid renderers.
if (geometryRenderer) {
geometryRenderer.call(this, geometry, data);
}
}
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawPointGeometry =
function(pointGeometry, data) {
var context = this.context_;
var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
var replay = replayGroup.getReplay(0, ol.render.ReplayType.IMAGE);
replay.setImageStyle(this.imageStyle_);
replay.drawPointGeometry(pointGeometry, data);
replay.finish(context);
// default colors
var opacity = 1;
var brightness = 0;
var contrast = 1;
var hue = 0;
var saturation = 1;
replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
this.size_, this.extent_, this.pixelRatio_, opacity, brightness,
contrast, hue, saturation, {});
replay.getDeleteResourcesFunction(context)();
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawLineStringGeometry =
function(lineStringGeometry, data) {
@@ -61,6 +200,7 @@ ol.render.webgl.Immediate.prototype.drawLineStringGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry =
function(multiLineStringGeometry, data) {
@@ -69,14 +209,32 @@ ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawMultiPointGeometry =
function(multiPointGeometry, data) {
var context = this.context_;
var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
var replay = replayGroup.getReplay(0, ol.render.ReplayType.IMAGE);
replay.setImageStyle(this.imageStyle_);
replay.drawMultiPointGeometry(multiPointGeometry, data);
replay.finish(context);
// default colors
var opacity = 1;
var brightness = 0;
var contrast = 1;
var hue = 0;
var saturation = 1;
replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
this.size_, this.extent_, this.pixelRatio_, opacity, brightness,
contrast, hue, saturation, {});
replay.getDeleteResourcesFunction(context)();
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry =
function(multiPolygonGeometry, data) {
@@ -85,6 +243,7 @@ ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawPolygonGeometry =
function(polygonGeometry, data) {
@@ -93,6 +252,7 @@ ol.render.webgl.Immediate.prototype.drawPolygonGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawText =
function(flatCoordinates, offset, end, stride, geometry, data) {
@@ -101,6 +261,7 @@ ol.render.webgl.Immediate.prototype.drawText =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.setFillStrokeStyle =
function(fillStyle, strokeStyle) {
@@ -109,13 +270,31 @@ ol.render.webgl.Immediate.prototype.setFillStrokeStyle =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) {
this.imageStyle_ = imageStyle;
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) {
};
/**
* @const
* @private
* @type {Object.<ol.geom.GeometryType,
* function(this: ol.render.webgl.Immediate, ol.geom.Geometry,
* Object)>}
*/
ol.render.webgl.Immediate.GEOMETRY_RENDERERS_ = {
'Point': ol.render.webgl.Immediate.prototype.drawPointGeometry,
'MultiPoint': ol.render.webgl.Immediate.prototype.drawMultiPointGeometry,
'GeometryCollection':
ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry
};

View File

@@ -0,0 +1,841 @@
goog.provide('ol.render.webgl.ImageReplay');
goog.provide('ol.render.webgl.ReplayGroup');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.functions');
goog.require('goog.object');
goog.require('goog.vec.Mat4');
goog.require('ol.color.Matrix');
goog.require('ol.extent');
goog.require('ol.render.IReplayGroup');
goog.require('ol.render.webgl.imagereplay.shader.Color');
goog.require('ol.render.webgl.imagereplay.shader.Default');
goog.require('ol.vec.Mat4');
goog.require('ol.webgl.Buffer');
/**
* @constructor
* @implements {ol.render.IVectorContext}
* @param {number} tolerance Tolerance.
* @param {ol.Extent} maxExtent Max extent.
* @protected
* @struct
*/
ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
/**
* @type {number|undefined}
* @private
*/
this.anchorX_ = undefined;
/**
* @type {number|undefined}
* @private
*/
this.anchorY_ = undefined;
/**
* @private
* @type {ol.color.Matrix}
*/
this.colorMatrix_ = new ol.color.Matrix();
/**
* The origin of the coordinate system for the point coordinates sent to
* the GPU. To eliminate jitter caused by precision problems in the GPU
* we use the "Rendering Relative to Eye" technique described in the "3D
* Engine Design for Virtual Globes" book.
* @private
* @type {ol.Coordinate}
*/
this.origin_ = ol.extent.getCenter(maxExtent);
/**
* @type {ol.Extent}
* @private
*/
this.extent_ = ol.extent.createEmpty();
/**
* @type {Array.<number>}
* @private
*/
this.groupIndices_ = [];
/**
* @type {number|undefined}
* @private
*/
this.height_ = undefined;
/**
* @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
* @private
*/
this.images_ = [];
/**
* @type {number|undefined}
* @private
*/
this.imageHeight_ = undefined;
/**
* @type {number|undefined}
* @private
*/
this.imageWidth_ = undefined;
/**
* @type {Array.<number>}
* @private
*/
this.indices_ = [];
/**
* @type {ol.webgl.Buffer}
* @private
*/
this.indicesBuffer_ = null;
/**
* @private
* @type {ol.render.webgl.imagereplay.shader.Color.Locations}
*/
this.colorLocations_ = null;
/**
* @private
* @type {ol.render.webgl.imagereplay.shader.Default.Locations}
*/
this.defaultLocations_ = null;
/**
* @private
* @type {number|undefined}
*/
this.opacity_ = undefined;
/**
* @type {!goog.vec.Mat4.Number}
* @private
*/
this.offsetRotateMatrix_ = goog.vec.Mat4.createNumberIdentity();
/**
* @type {!goog.vec.Mat4.Number}
* @private
*/
this.offsetScaleMatrix_ = goog.vec.Mat4.createNumberIdentity();
/**
* @type {number|undefined}
* @private
*/
this.originX_ = undefined;
/**
* @type {number|undefined}
* @private
*/
this.originY_ = undefined;
/**
* @type {!goog.vec.Mat4.Number}
* @private
*/
this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity();
/**
* @private
* @type {boolean|undefined}
*/
this.rotateWithView_ = undefined;
/**
* @private
* @type {number|undefined}
*/
this.rotation_ = undefined;
/**
* @private
* @type {number|undefined}
*/
this.scale_ = undefined;
/**
* @type {Array.<WebGLTexture>}
* @private
*/
this.textures_ = [];
/**
* @type {Array.<number>}
* @private
*/
this.vertices_ = [];
/**
* @type {ol.webgl.Buffer}
* @private
*/
this.verticesBuffer_ = null;
/**
* @type {number|undefined}
* @private
*/
this.width_ = undefined;
};
/**
* @param {ol.webgl.Context} context WebGL context.
* @return {function()} Delete resources function.
*/
ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction =
function(context) {
// We only delete our stuff here. The shaders and the program may
// be used by other ImageReplay instances (for other layers). And
// they will be deleted when disposing of the ol.webgl.Context
// object.
goog.asserts.assert(!goog.isNull(this.verticesBuffer_));
goog.asserts.assert(!goog.isNull(this.indicesBuffer_));
var verticesBuffer = this.verticesBuffer_;
var indicesBuffer = this.indicesBuffer_;
var textures = this.textures_;
var gl = context.getGL();
return function() {
if (!gl.isContextLost()) {
var i, ii;
for (i = 0, ii = textures.length; i < ii; ++i) {
gl.deleteTexture(textures[i]);
}
}
context.deleteBuffer(verticesBuffer);
context.deleteBuffer(indicesBuffer);
};
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawAsync = goog.abstractMethod;
/**
* @param {Array.<number>} flatCoordinates Flat coordinates.
* @param {number} offset Offset.
* @param {number} end End.
* @param {number} stride Stride.
* @return {number} My end.
* @private
*/
ol.render.webgl.ImageReplay.prototype.drawCoordinates_ =
function(flatCoordinates, offset, end, stride) {
goog.asserts.assert(goog.isDef(this.anchorX_));
goog.asserts.assert(goog.isDef(this.anchorY_));
goog.asserts.assert(goog.isDef(this.height_));
goog.asserts.assert(goog.isDef(this.imageHeight_));
goog.asserts.assert(goog.isDef(this.imageWidth_));
goog.asserts.assert(goog.isDef(this.opacity_));
goog.asserts.assert(goog.isDef(this.originX_));
goog.asserts.assert(goog.isDef(this.originY_));
goog.asserts.assert(goog.isDef(this.rotateWithView_));
goog.asserts.assert(goog.isDef(this.rotation_));
goog.asserts.assert(goog.isDef(this.scale_));
goog.asserts.assert(goog.isDef(this.width_));
var anchorX = this.anchorX_;
var anchorY = this.anchorY_;
var height = this.height_;
var imageHeight = this.imageHeight_;
var imageWidth = this.imageWidth_;
var opacity = this.opacity_;
var originX = this.originX_;
var originY = this.originY_;
var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0;
var rotation = this.rotation_;
var scale = this.scale_;
var width = this.width_;
var cos = Math.cos(rotation);
var sin = Math.sin(rotation);
var numIndices = this.indices_.length;
var numVertices = this.vertices_.length;
var i, n, offsetX, offsetY, x, y;
for (i = offset; i < end; i += stride) {
x = flatCoordinates[i] - this.origin_[0];
y = flatCoordinates[i + 1] - this.origin_[1];
// There are 4 vertices per [x, y] point, one for each corner of the
// rectangle we're going to draw. We'd use 1 vertex per [x, y] point if
// WebGL supported Geometry Shaders (which can emit new vertices), but that
// is not currently the case.
//
// And each vertex includes 8 values: the x and y coordinates, the x and
// y offsets used to calculate the position of the corner, the u and
// v texture coordinates for the corner, the opacity, and whether the
// the image should be rotated with the view (rotateWithView).
n = numVertices / 8;
// bottom-left corner
offsetX = -scale * anchorX;
offsetY = -scale * (height - anchorY);
this.vertices_[numVertices++] = x;
this.vertices_[numVertices++] = y;
this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
this.vertices_[numVertices++] = originX / imageWidth;
this.vertices_[numVertices++] = (originY + height) / imageHeight;
this.vertices_[numVertices++] = opacity;
this.vertices_[numVertices++] = rotateWithView;
// bottom-right corner
offsetX = scale * (width - anchorX);
offsetY = -scale * (height - anchorY);
this.vertices_[numVertices++] = x;
this.vertices_[numVertices++] = y;
this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
this.vertices_[numVertices++] = (originX + width) / imageWidth;
this.vertices_[numVertices++] = (originY + height) / imageHeight;
this.vertices_[numVertices++] = opacity;
this.vertices_[numVertices++] = rotateWithView;
// top-right corner
offsetX = scale * (width - anchorX);
offsetY = scale * anchorY;
this.vertices_[numVertices++] = x;
this.vertices_[numVertices++] = y;
this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
this.vertices_[numVertices++] = (originX + width) / imageWidth;
this.vertices_[numVertices++] = originY / imageHeight;
this.vertices_[numVertices++] = opacity;
this.vertices_[numVertices++] = rotateWithView;
// top-left corner
offsetX = -scale * anchorX;
offsetY = scale * anchorY;
this.vertices_[numVertices++] = x;
this.vertices_[numVertices++] = y;
this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
this.vertices_[numVertices++] = originX / imageWidth;
this.vertices_[numVertices++] = originY / imageHeight;
this.vertices_[numVertices++] = opacity;
this.vertices_[numVertices++] = rotateWithView;
this.indices_[numIndices++] = n;
this.indices_[numIndices++] = n + 1;
this.indices_[numIndices++] = n + 2;
this.indices_[numIndices++] = n;
this.indices_[numIndices++] = n + 2;
this.indices_[numIndices++] = n + 3;
}
return numVertices;
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawCircleGeometry = goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawFeature = goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawGeometryCollectionGeometry =
goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawLineStringGeometry =
goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawMultiLineStringGeometry =
goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawMultiPointGeometry =
function(multiPointGeometry, data) {
ol.extent.extend(this.extent_, multiPointGeometry.getExtent());
var flatCoordinates = multiPointGeometry.getFlatCoordinates();
var stride = multiPointGeometry.getStride();
this.drawCoordinates_(
flatCoordinates, 0, flatCoordinates.length, stride);
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawMultiPolygonGeometry =
goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawPointGeometry =
function(pointGeometry, data) {
ol.extent.extend(this.extent_, pointGeometry.getExtent());
var flatCoordinates = pointGeometry.getFlatCoordinates();
var stride = pointGeometry.getStride();
this.drawCoordinates_(
flatCoordinates, 0, flatCoordinates.length, stride);
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawPolygonGeometry = goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawText = goog.abstractMethod;
/**
* @param {ol.webgl.Context} context Context.
*/
ol.render.webgl.ImageReplay.prototype.finish = function(context) {
var gl = context.getGL();
this.groupIndices_.push(this.indices_.length);
goog.asserts.assert(this.images_.length == this.groupIndices_.length);
// create, bind, and populate the vertices buffer
this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_);
context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_);
var indices = this.indices_;
var bits = context.hasOESElementIndexUint ? 32 : 16;
goog.asserts.assert(indices[indices.length - 1] < Math.pow(2, bits),
'Too large element index detected [%s] (OES_element_index_uint "%s")',
indices[indices.length - 1], context.hasOESElementIndexUint);
// create, bind, and populate the indices buffer
this.indicesBuffer_ = new ol.webgl.Buffer(indices);
context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
goog.asserts.assert(this.textures_.length === 0);
// create textures
var texture, image, uid;
/** @type {Object.<string, WebGLTexture>} */
var texturePerImage = {};
var i;
var ii = this.images_.length;
for (i = 0; i < ii; ++i) {
image = this.images_[i];
uid = goog.getUid(image).toString();
if (goog.object.containsKey(texturePerImage, uid)) {
texture = goog.object.get(texturePerImage, uid);
} else {
texture = gl.createTexture();
gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
gl.texParameteri(goog.webgl.TEXTURE_2D,
goog.webgl.TEXTURE_WRAP_S, goog.webgl.CLAMP_TO_EDGE);
gl.texParameteri(goog.webgl.TEXTURE_2D,
goog.webgl.TEXTURE_WRAP_T, goog.webgl.CLAMP_TO_EDGE);
gl.texParameteri(goog.webgl.TEXTURE_2D,
goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR);
gl.texParameteri(goog.webgl.TEXTURE_2D,
goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR);
gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, goog.webgl.RGBA,
goog.webgl.UNSIGNED_BYTE, image);
goog.object.set(texturePerImage, uid, texture);
}
this.textures_[i] = texture;
}
goog.asserts.assert(this.textures_.length == this.groupIndices_.length);
this.anchorX_ = undefined;
this.anchorY_ = undefined;
this.height_ = undefined;
this.images_ = null;
this.imageHeight_ = undefined;
this.imageWidth_ = undefined;
this.indices_ = null;
this.opacity_ = undefined;
this.originX_ = undefined;
this.originY_ = undefined;
this.rotateWithView_ = undefined;
this.rotation_ = undefined;
this.scale_ = undefined;
this.vertices_ = null;
this.width_ = undefined;
};
/**
* @return {ol.Extent} Extent.
*/
ol.render.webgl.ImageReplay.prototype.getExtent = function() {
return this.extent_;
};
/**
* @param {ol.webgl.Context} context Context.
* @param {ol.Coordinate} center Center.
* @param {number} resolution Resolution.
* @param {number} rotation Rotation.
* @param {ol.Size} size Size.
* @param {ol.Extent} extent Extent.
* @param {number} pixelRatio Pixel ratio.
* @param {number} opacity Global opacity.
* @param {number} brightness Global brightness.
* @param {number} contrast Global contrast.
* @param {number} hue Global hue.
* @param {number} saturation Global saturation.
* @param {Object} skippedFeaturesHash Ids of features to skip.
* @return {T|undefined} Callback result.
* @template T
*/
ol.render.webgl.ImageReplay.prototype.replay = function(context,
center, resolution, rotation, size, extent, pixelRatio,
opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) {
var gl = context.getGL();
// bind the vertices buffer
goog.asserts.assert(!goog.isNull(this.verticesBuffer_));
context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_);
// bind the indices buffer
goog.asserts.assert(!goog.isNull(this.indicesBuffer_));
context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
var useColor = brightness || contrast != 1 || hue || saturation != 1;
// get the program
var fragmentShader, vertexShader;
if (useColor) {
fragmentShader =
ol.render.webgl.imagereplay.shader.ColorFragment.getInstance();
vertexShader =
ol.render.webgl.imagereplay.shader.ColorVertex.getInstance();
} else {
fragmentShader =
ol.render.webgl.imagereplay.shader.DefaultFragment.getInstance();
vertexShader =
ol.render.webgl.imagereplay.shader.DefaultVertex.getInstance();
}
var program = context.getProgram(fragmentShader, vertexShader);
// get the locations
var locations;
if (useColor) {
if (goog.isNull(this.colorLocations_)) {
locations =
new ol.render.webgl.imagereplay.shader.Color.Locations(gl, program);
this.colorLocations_ = locations;
} else {
locations = this.colorLocations_;
}
} else {
if (goog.isNull(this.defaultLocations_)) {
locations =
new ol.render.webgl.imagereplay.shader.Default.Locations(gl, program);
this.defaultLocations_ = locations;
} else {
locations = this.defaultLocations_;
}
}
// use the program (FIXME: use the return value)
context.useProgram(program);
// enable the vertex attrib arrays
gl.enableVertexAttribArray(locations.a_position);
gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT,
false, 32, 0);
gl.enableVertexAttribArray(locations.a_offsets);
gl.vertexAttribPointer(locations.a_offsets, 2, goog.webgl.FLOAT,
false, 32, 8);
gl.enableVertexAttribArray(locations.a_texCoord);
gl.vertexAttribPointer(locations.a_texCoord, 2, goog.webgl.FLOAT,
false, 32, 16);
gl.enableVertexAttribArray(locations.a_opacity);
gl.vertexAttribPointer(locations.a_opacity, 1, goog.webgl.FLOAT,
false, 32, 24);
gl.enableVertexAttribArray(locations.a_rotateWithView);
gl.vertexAttribPointer(locations.a_rotateWithView, 1, goog.webgl.FLOAT,
false, 32, 28);
// set the "uniform" values
var projectionMatrix = this.projectionMatrix_;
ol.vec.Mat4.makeTransform2D(projectionMatrix,
0.0, 0.0,
2 / (resolution * size[0]),
2 / (resolution * size[1]),
-rotation,
-(center[0] - this.origin_[0]), -(center[1] - this.origin_[1]));
var offsetScaleMatrix = this.offsetScaleMatrix_;
goog.vec.Mat4.makeScale(offsetScaleMatrix, 2 / size[0], 2 / size[1], 1);
var offsetRotateMatrix = this.offsetRotateMatrix_;
goog.vec.Mat4.makeIdentity(offsetRotateMatrix);
if (rotation !== 0) {
goog.vec.Mat4.rotateZ(offsetRotateMatrix, -rotation);
}
gl.uniformMatrix4fv(locations.u_projectionMatrix, false, projectionMatrix);
gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, offsetScaleMatrix);
gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false,
offsetRotateMatrix);
gl.uniform1f(locations.u_opacity, opacity);
if (useColor) {
gl.uniformMatrix4fv(locations.u_colorMatrix, false,
this.colorMatrix_.getMatrix(brightness, contrast, hue, saturation));
}
// draw!
goog.asserts.assert(this.textures_.length == this.groupIndices_.length);
var i, ii, start;
for (i = 0, ii = this.textures_.length, start = 0; i < ii; ++i) {
gl.bindTexture(goog.webgl.TEXTURE_2D, this.textures_[i]);
var end = this.groupIndices_[i];
var numItems = end - start;
var offsetInBytes = start * (context.hasOESElementIndexUint ? 4 : 2);
var elementType = context.hasOESElementIndexUint ?
goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT;
gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes);
start = end;
}
// disable the vertex attrib arrays
gl.disableVertexAttribArray(locations.a_position);
gl.disableVertexAttribArray(locations.a_offsets);
gl.disableVertexAttribArray(locations.a_texCoord);
gl.disableVertexAttribArray(locations.a_opacity);
gl.disableVertexAttribArray(locations.a_rotateWithView);
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.setFillStrokeStyle = goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) {
var anchor = imageStyle.getAnchor();
goog.asserts.assert(!goog.isNull(anchor));
var image = imageStyle.getImage(1);
goog.asserts.assert(!goog.isNull(image));
var imageSize = imageStyle.getImageSize();
goog.asserts.assert(!goog.isNull(imageSize));
var opacity = imageStyle.getOpacity();
goog.asserts.assert(goog.isDef(opacity));
var origin = imageStyle.getOrigin();
goog.asserts.assert(!goog.isNull(origin));
var rotateWithView = imageStyle.getRotateWithView();
goog.asserts.assert(goog.isDef(rotateWithView));
var rotation = imageStyle.getRotation();
goog.asserts.assert(goog.isDef(rotation));
var size = imageStyle.getSize();
goog.asserts.assert(!goog.isNull(size));
var scale = imageStyle.getScale();
goog.asserts.assert(goog.isDef(scale));
if (this.images_.length === 0) {
this.images_.push(image);
} else {
var currentImage = this.images_[this.images_.length - 1];
if (goog.getUid(currentImage) != goog.getUid(image)) {
this.groupIndices_.push(this.indices_.length);
goog.asserts.assert(this.groupIndices_.length == this.images_.length);
this.images_.push(image);
}
}
this.anchorX_ = anchor[0];
this.anchorY_ = anchor[1];
this.height_ = size[1];
this.imageHeight_ = imageSize[1];
this.imageWidth_ = imageSize[0];
this.opacity_ = opacity;
this.originX_ = origin[0];
this.originY_ = origin[1];
this.rotation_ = rotation;
this.rotateWithView_ = rotateWithView;
this.scale_ = scale;
this.width_ = size[0];
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.setTextStyle = goog.abstractMethod;
/**
* @constructor
* @implements {ol.render.IReplayGroup}
* @param {number} tolerance Tolerance.
* @param {ol.Extent} maxExtent Max extent.
* @struct
*/
ol.render.webgl.ReplayGroup = function(tolerance, maxExtent) {
/**
* @type {ol.Extent}
* @private
*/
this.maxExtent_ = maxExtent;
/**
* @type {number}
* @private
*/
this.tolerance_ = tolerance;
/**
* ImageReplay only is supported at this point.
* @type {Object.<ol.render.ReplayType, ol.render.webgl.ImageReplay>}
* @private
*/
this.replays_ = {};
};
/**
* @param {ol.webgl.Context} context WebGL context.
* @return {function()} Delete resources function.
*/
ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction =
function(context) {
var functions = [];
var replayKey;
for (replayKey in this.replays_) {
functions.push(
this.replays_[replayKey].getDeleteResourcesFunction(context));
}
return goog.functions.sequence.apply(null, functions);
};
/**
* @param {ol.webgl.Context} context Context.
*/
ol.render.webgl.ReplayGroup.prototype.finish = function(context) {
var replayKey;
for (replayKey in this.replays_) {
this.replays_[replayKey].finish(context);
}
};
/**
* @inheritDoc
*/
ol.render.webgl.ReplayGroup.prototype.getReplay =
function(zIndex, replayType) {
var replay = this.replays_[replayType];
if (!goog.isDef(replay)) {
var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType];
goog.asserts.assert(goog.isDef(constructor));
replay = new constructor(this.tolerance_, this.maxExtent_);
this.replays_[replayType] = replay;
}
return replay;
};
/**
* @inheritDoc
*/
ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
return goog.object.isEmpty(this.replays_);
};
/**
* @param {ol.webgl.Context} context Context.
* @param {ol.Coordinate} center Center.
* @param {number} resolution Resolution.
* @param {number} rotation Rotation.
* @param {ol.Size} size Size.
* @param {ol.Extent} extent Extent.
* @param {number} pixelRatio Pixel ratio.
* @param {number} opacity Global opacity.
* @param {number} brightness Global brightness.
* @param {number} contrast Global contrast.
* @param {number} hue Global hue.
* @param {number} saturation Global saturation.
* @param {Object} skippedFeaturesHash Ids of features to skip.
* @return {T|undefined} Callback result.
* @template T
*/
ol.render.webgl.ReplayGroup.prototype.replay = function(context,
center, resolution, rotation, size, extent, pixelRatio,
opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) {
var i, ii, replay, result;
for (i = 0, ii = ol.render.REPLAY_ORDER.length; i < ii; ++i) {
replay = this.replays_[ol.render.REPLAY_ORDER[i]];
if (goog.isDef(replay) &&
ol.extent.intersects(extent, replay.getExtent())) {
result = replay.replay(context,
center, resolution, rotation, size, extent, pixelRatio,
opacity, brightness, contrast, hue, saturation, skippedFeaturesHash);
if (result) {
return result;
}
}
}
return undefined;
};
/**
* @const
* @private
* @type {Object.<ol.render.ReplayType,
* function(new: ol.render.webgl.ImageReplay, number,
* ol.Extent)>}
*/
ol.render.webgl.BATCH_CONSTRUCTORS_ = {
'Image': ol.render.webgl.ImageReplay
};

View File

@@ -207,6 +207,14 @@ ol.renderer.canvas.Layer.prototype.getTransform = function(frameState) {
};
/**
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @return {boolean} whether composeFrame should be called.
*/
ol.renderer.canvas.Layer.prototype.prepareFrame = goog.abstractMethod;
/**
* @param {ol.Size} size Size.
* @return {boolean} True when the canvas with the current size does not exceed

View File

@@ -46,3 +46,11 @@ ol.renderer.dom.Layer.prototype.composeFrame = goog.nullFunction;
ol.renderer.dom.Layer.prototype.getTarget = function() {
return this.target;
};
/**
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @return {boolean} whether composeFrame should be called.
*/
ol.renderer.dom.Layer.prototype.prepareFrame = goog.abstractMethod;

View File

@@ -6,7 +6,6 @@ goog.require('ol.ImageState');
goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.layer.Layer');
goog.require('ol.layer.LayerState');
goog.require('ol.source.Source');
goog.require('ol.source.State');
goog.require('ol.source.Tile');
@@ -95,14 +94,6 @@ ol.renderer.Layer.prototype.handleImageChange = function(event) {
};
/**
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @return {boolean} whether composeFrame should be called.
*/
ol.renderer.Layer.prototype.prepareFrame = goog.abstractMethod;
/**
* @protected
*/

View File

@@ -101,7 +101,7 @@ ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtPixel =
* @inheritDoc
*/
ol.renderer.webgl.ImageLayer.prototype.prepareFrame =
function(frameState, layerState) {
function(frameState, layerState, context) {
var gl = this.getWebGLMapRenderer().getGL();

View File

@@ -10,7 +10,7 @@ goog.require('ol.render.webgl.Immediate');
goog.require('ol.renderer.Layer');
goog.require('ol.renderer.webgl.map.shader.Color');
goog.require('ol.renderer.webgl.map.shader.Default');
goog.require('ol.structs.Buffer');
goog.require('ol.webgl.Buffer');
@@ -26,9 +26,9 @@ ol.renderer.webgl.Layer = function(mapRenderer, layer) {
/**
* @private
* @type {ol.structs.Buffer}
* @type {ol.webgl.Buffer}
*/
this.arrayBuffer_ = new ol.structs.Buffer([
this.arrayBuffer_ = new ol.webgl.Buffer([
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
@@ -237,7 +237,16 @@ ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ =
function(type, context, frameState) {
var layer = this.getLayer();
if (layer.hasListener(type)) {
var render = new ol.render.webgl.Immediate(context, frameState.pixelRatio);
var viewState = frameState.viewState;
var resolution = viewState.resolution;
var pixelRatio = frameState.pixelRatio;
var extent = frameState.extent;
var center = viewState.center;
var rotation = viewState.rotation;
var size = frameState.size;
var render = new ol.render.webgl.Immediate(
context, center, resolution, rotation, size, extent, pixelRatio);
var composeEvent = new ol.render.Event(
type, layer, render, null, frameState, null, context);
layer.dispatchEvent(composeEvent);
@@ -286,3 +295,12 @@ ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
this.framebuffer = null;
this.framebufferDimension = undefined;
};
/**
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @param {ol.webgl.Context} context Context.
* @return {boolean} whether composeFrame should be called.
*/
ol.renderer.webgl.Layer.prototype.prepareFrame = goog.abstractMethod;

View File

@@ -20,13 +20,17 @@ goog.require('ol.dom');
goog.require('ol.layer.Image');
goog.require('ol.layer.Layer');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Vector');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.webgl.Immediate');
goog.require('ol.render.webgl.ReplayGroup');
goog.require('ol.renderer.Map');
goog.require('ol.renderer.vector');
goog.require('ol.renderer.webgl.ImageLayer');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.TileLayer');
goog.require('ol.renderer.webgl.VectorLayer');
goog.require('ol.source.State');
goog.require('ol.structs.LRUCache');
goog.require('ol.structs.PriorityQueue');
@@ -250,6 +254,8 @@ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) {
return new ol.renderer.webgl.ImageLayer(this, layer);
} else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
return new ol.renderer.webgl.TileLayer(this, layer);
} else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
return new ol.renderer.webgl.VectorLayer(this, layer);
} else {
goog.asserts.fail();
return null;
@@ -266,11 +272,40 @@ ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ =
function(type, frameState) {
var map = this.getMap();
if (map.hasListener(type)) {
var context = this.getContext();
var render = new ol.render.webgl.Immediate(context, frameState.pixelRatio);
var composeEvent = new ol.render.Event(
type, map, render, null, frameState, null, context);
var context = this.context_;
var extent = frameState.extent;
var size = frameState.size;
var viewState = frameState.viewState;
var pixelRatio = frameState.pixelRatio;
var resolution = viewState.resolution;
var center = viewState.center;
var rotation = viewState.rotation;
var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
var vectorContext = new ol.render.webgl.Immediate(context,
center, resolution, rotation, size, extent, pixelRatio);
var replayGroup = new ol.render.webgl.ReplayGroup(tolerance, extent);
var composeEvent = new ol.render.Event(type, map, vectorContext,
replayGroup, frameState, null, context);
map.dispatchEvent(composeEvent);
replayGroup.finish(context);
if (!replayGroup.isEmpty()) {
// use default color values
var opacity = 1;
var brightness = 0;
var contrast = 1;
var hue = 0;
var saturation = 1;
replayGroup.replay(context, center, resolution, rotation, size, extent,
pixelRatio, opacity, brightness, contrast, hue, saturation, {});
}
replayGroup.getDeleteResourcesFunction(context)();
vectorContext.flush();
this.replayGroup = replayGroup;
}
};
@@ -455,7 +490,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
layerState.sourceState == ol.source.State.READY) {
layerRenderer = this.getLayerRenderer(layerState.layer);
goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer);
if (layerRenderer.prepareFrame(frameState, layerState)) {
if (layerRenderer.prepareFrame(frameState, layerState, context)) {
layerStatesToDraw.push(layerState);
}
}

View File

@@ -16,8 +16,8 @@ goog.require('ol.layer.Tile');
goog.require('ol.math');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.tilelayer.shader');
goog.require('ol.structs.Buffer');
goog.require('ol.tilecoord');
goog.require('ol.webgl.Buffer');
@@ -52,9 +52,9 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {
/**
* @private
* @type {ol.structs.Buffer}
* @type {ol.webgl.Buffer}
*/
this.renderArrayBuffer_ = new ol.structs.Buffer([
this.renderArrayBuffer_ = new ol.webgl.Buffer([
0, 0, 0, 1,
1, 0, 1, 1,
0, 1, 0, 0,
@@ -107,11 +107,10 @@ ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
* @inheritDoc
*/
ol.renderer.webgl.TileLayer.prototype.prepareFrame =
function(frameState, layerState) {
function(frameState, layerState, context) {
var mapRenderer = this.getWebGLMapRenderer();
var context = mapRenderer.getContext();
var gl = mapRenderer.getGL();
var gl = context.getGL();
var viewState = frameState.viewState;
var projection = viewState.projection;

View File

@@ -0,0 +1,240 @@
goog.provide('ol.renderer.webgl.VectorLayer');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('ol.ViewHint');
goog.require('ol.extent');
goog.require('ol.layer.Vector');
goog.require('ol.render.webgl.ReplayGroup');
goog.require('ol.renderer.vector');
goog.require('ol.renderer.webgl.Layer');
/**
* @constructor
* @extends {ol.renderer.webgl.Layer}
* @param {ol.renderer.Map} mapRenderer Map renderer.
* @param {ol.layer.Vector} vectorLayer Vector layer.
*/
ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) {
goog.base(this, mapRenderer, vectorLayer);
/**
* @private
* @type {boolean}
*/
this.dirty_ = false;
/**
* @private
* @type {number}
*/
this.renderedRevision_ = -1;
/**
* @private
* @type {number}
*/
this.renderedResolution_ = NaN;
/**
* @private
* @type {ol.Extent}
*/
this.renderedExtent_ = ol.extent.createEmpty();
/**
* @private
* @type {function(ol.Feature, ol.Feature): number|null}
*/
this.renderedRenderOrder_ = null;
/**
* @private
* @type {ol.render.webgl.ReplayGroup}
*/
this.replayGroup_ = null;
};
goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
/**
* @inheritDoc
*/
ol.renderer.webgl.VectorLayer.prototype.composeFrame =
function(frameState, layerState, context) {
var viewState = frameState.viewState;
var replayGroup = this.replayGroup_;
if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) {
replayGroup.replay(context,
viewState.center, viewState.resolution, viewState.rotation,
frameState.size, frameState.extent, frameState.pixelRatio,
layerState.opacity, layerState.brightness, layerState.contrast,
layerState.hue, layerState.saturation, frameState.skippedFeatureUids);
}
};
/**
* @inheritDoc
*/
ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() {
var replayGroup = this.replayGroup_;
if (!goog.isNull(replayGroup)) {
var mapRenderer = this.getWebGLMapRenderer();
var context = mapRenderer.getContext();
replayGroup.getDeleteResourcesFunction(context)();
this.replayGroup_ = null;
}
goog.base(this, 'disposeInternal');
};
/**
* @inheritDoc
*/
ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel =
function(coordinate, frameState, callback, thisArg) {
};
/**
* Handle changes in image style state.
* @param {goog.events.Event} event Image style change event.
* @private
*/
ol.renderer.webgl.VectorLayer.prototype.handleImageChange_ =
function(event) {
this.renderIfReadyAndVisible();
};
/**
* @inheritDoc
*/
ol.renderer.webgl.VectorLayer.prototype.prepareFrame =
function(frameState, layerState, context) {
var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector);
var vectorSource = vectorLayer.getSource();
this.updateAttributions(
frameState.attributions, vectorSource.getAttributions());
this.updateLogos(frameState, vectorSource);
if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] ||
frameState.viewHints[ol.ViewHint.INTERACTING])) {
return true;
}
var frameStateExtent = frameState.extent;
var viewState = frameState.viewState;
var projection = viewState.projection;
var resolution = viewState.resolution;
var pixelRatio = frameState.pixelRatio;
var vectorLayerRevision = vectorLayer.getRevision();
var vectorLayerRenderOrder = vectorLayer.getRenderOrder();
if (!goog.isDef(vectorLayerRenderOrder)) {
vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
}
if (!this.dirty_ &&
this.renderedResolution_ == resolution &&
this.renderedRevision_ == vectorLayerRevision &&
this.renderedRenderOrder_ == vectorLayerRenderOrder &&
ol.extent.containsExtent(this.renderedExtent_, frameStateExtent)) {
return true;
}
var extent = this.renderedExtent_;
var xBuffer = ol.extent.getWidth(frameStateExtent) / 4;
var yBuffer = ol.extent.getHeight(frameStateExtent) / 4;
extent[0] = frameStateExtent[0] - xBuffer;
extent[1] = frameStateExtent[1] - yBuffer;
extent[2] = frameStateExtent[2] + xBuffer;
extent[3] = frameStateExtent[3] + yBuffer;
if (!goog.isNull(this.replayGroup_)) {
frameState.postRenderFunctions.push(
this.replayGroup_.getDeleteResourcesFunction(context));
}
this.dirty_ = false;
var replayGroup = new ol.render.webgl.ReplayGroup(
ol.renderer.vector.getTolerance(resolution, pixelRatio),
extent);
vectorSource.loadFeatures(extent, resolution, projection);
var renderFeature =
/**
* @param {ol.Feature} feature Feature.
* @this {ol.renderer.webgl.VectorLayer}
*/
function(feature) {
var styles;
if (goog.isDef(feature.getStyleFunction())) {
styles = feature.getStyleFunction().call(feature, resolution);
} else if (goog.isDef(vectorLayer.getStyleFunction())) {
styles = vectorLayer.getStyleFunction()(feature, resolution);
}
if (goog.isDefAndNotNull(styles)) {
var dirty = this.renderFeature(
feature, resolution, pixelRatio, styles, replayGroup);
this.dirty_ = this.dirty_ || dirty;
}
};
if (!goog.isNull(vectorLayerRenderOrder)) {
/** @type {Array.<ol.Feature>} */
var features = [];
vectorSource.forEachFeatureInExtentAtResolution(extent, resolution,
/**
* @param {ol.Feature} feature Feature.
*/
function(feature) {
features.push(feature);
}, this);
goog.array.sort(features, vectorLayerRenderOrder);
goog.array.forEach(features, renderFeature, this);
} else {
vectorSource.forEachFeatureInExtentAtResolution(
extent, resolution, renderFeature, this);
}
replayGroup.finish(context);
this.renderedResolution_ = resolution;
this.renderedRevision_ = vectorLayerRevision;
this.renderedRenderOrder_ = vectorLayerRenderOrder;
this.replayGroup_ = replayGroup;
return true;
};
/**
* @param {ol.Feature} feature Feature.
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
* @param {Array.<ol.style.Style>} styles Array of styles
* @param {ol.render.webgl.ReplayGroup} replayGroup Replay group.
* @return {boolean} `true` if an image is loading.
*/
ol.renderer.webgl.VectorLayer.prototype.renderFeature =
function(feature, resolution, pixelRatio, styles, replayGroup) {
if (!goog.isDefAndNotNull(styles)) {
return false;
}
var i, ii, loading = false;
for (i = 0, ii = styles.length; i < ii; ++i) {
loading = ol.renderer.vector.renderFeature(
replayGroup, feature, styles[i],
ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
feature, this.handleImageChange_, this) || loading;
}
return loading;
};

View File

@@ -1,257 +0,0 @@
goog.provide('ol.structs.Buffer');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.webgl');
goog.require('ol');
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
};
/**
* @constructor
* @param {Array.<number>=} opt_arr Array.
* @param {number=} opt_used Used.
* @param {number=} opt_usage Usage.
* @struct
*/
ol.structs.Buffer = function(opt_arr, opt_used, opt_usage) {
/**
* @private
* @type {Array.<number>}
*/
this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
/**
* @private
* @type {Array.<ol.structs.IntegerSet>}
*/
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 {?Float32Array}
*/
this.split32_ = null;
/**
* @private
* @type {ol.structs.IntegerSet}
*/
this.split32DirtySet_ = null;
/**
* @private
* @type {number}
*/
this.usage_ = goog.isDef(opt_usage) ?
opt_usage : ol.structs.BufferUsage.STATIC_DRAW;
};
/**
* @param {number} size Size.
* @return {number} Offset.
*/
ol.structs.Buffer.prototype.allocate = function(size) {
goog.asserts.assert(size > 0);
var offset = this.freeSet_.findRange(size);
goog.asserts.assert(offset != -1); // FIXME
this.freeSet_.removeRange(offset, offset + size);
return offset;
};
/**
* @param {Array.<number>} values Values.
* @return {number} Offset.
*/
ol.structs.Buffer.prototype.add = function(values) {
var size = values.length;
var offset = this.allocate(size);
var i;
for (i = 0; i < size; ++i) {
this.arr_[offset + i] = values[i];
}
this.markDirty(size, offset);
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_this The object to use as `this` in `f`.
* @template T
*/
ol.structs.Buffer.prototype.forEachRange = function(f, opt_this) {
if (this.arr_.length !== 0) {
this.freeSet_.forEachRangeInverted(0, this.arr_.length, f, opt_this);
}
};
/**
* @return {Array.<number>} 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_;
};
/**
* Returns a Float32Array twice the length of the buffer containing each value
* split into two 32-bit floating point values that, when added together,
* approximate the original value. Even indicies contain the high bits, odd
* indicies contain the low bits.
* @see http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/
* @return {Float32Array} Split.
*/
ol.structs.Buffer.prototype.getSplit32 = function() {
var arr = this.arr_;
var n = arr.length;
if (goog.isNull(this.split32DirtySet_)) {
this.split32DirtySet_ = new ol.structs.IntegerSet([0, n]);
this.addDirtySet(this.split32DirtySet_);
}
if (goog.isNull(this.split32_)) {
this.split32_ = new Float32Array(2 * n);
}
var split32 = this.split32_;
this.split32DirtySet_.forEachRange(function(start, stop) {
var doubleHigh, i, j, value;
for (i = start, j = 2 * start; i < stop; ++i, j += 2) {
value = arr[i];
if (value < 0) {
doubleHigh = 65536 * Math.floor(-value / 65536);
split32[j] = -doubleHigh;
split32[j + 1] = value + doubleHigh;
} else {
doubleHigh = 65536 * Math.floor(value / 65536);
split32[j] = doubleHigh;
split32[j + 1] = value - doubleHigh;
}
}
});
this.split32DirtySet_.clear();
return this.split32_;
};
/**
* @return {number} Usage.
*/
ol.structs.Buffer.prototype.getUsage = function() {
return this.usage_;
};
/**
* @param {number} size Size.
* @param {number} offset Offset.
*/
ol.structs.Buffer.prototype.markDirty = function(size, offset) {
var i, ii;
for (i = 0, ii = this.dirtySets_.length; i < ii; ++i) {
this.dirtySets_[i].addRange(offset, offset + size);
}
};
/**
* @param {number} size Size.
* @param {number} offset Offset.
*/
ol.structs.Buffer.prototype.remove = function(size, offset) {
var i, ii;
this.freeSet_.addRange(offset, offset + size);
for (i = 0, ii = this.dirtySets_.length; i < ii; ++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.<number>} 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];
}
this.markDirty(n, offset);
};

View File

@@ -0,0 +1,16 @@
goog.provide('ol.structs.IHasChecksum');
/**
* @interface
*/
ol.structs.IHasChecksum = function() {
};
/**
* @return {string} The checksum.
*/
ol.structs.IHasChecksum.prototype.getChecksum = function() {
};

View File

@@ -1,330 +0,0 @@
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
* @struct
* @param {Array.<number>=} opt_arr Array.
*/
ol.structs.IntegerSet = function(opt_arr) {
/**
* @private
* @type {Array.<number>}
*/
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 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_this The object to use as `this` in `f`.
* @template T
*/
ol.structs.IntegerSet.prototype.forEachRange = function(f, opt_this) {
var arr = this.arr_;
var n = arr.length;
var i;
for (i = 0; i < n; i += 2) {
f.call(opt_this, 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_this The object to use as `this` in `f`.
* @template T
*/
ol.structs.IntegerSet.prototype.forEachRangeInverted =
function(start, stop, f, opt_this) {
goog.asserts.assert(start < stop);
var arr = this.arr_;
var n = arr.length;
if (n === 0) {
f.call(opt_this, start, stop);
} else {
if (start < arr[0]) {
f.call(opt_this, start, arr[0]);
}
var i;
for (i = 1; i < n - 1; i += 2) {
f.call(opt_this, arr[i], arr[i + 1]);
}
if (arr[n - 1] < stop) {
f.call(opt_this, arr[n - 1], stop);
}
}
};
/**
* @return {Array.<number>} 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.<number>} 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(', ');
};
}

View File

@@ -0,0 +1,440 @@
goog.provide('ol.style.Atlas');
goog.provide('ol.style.AtlasManager');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.object');
goog.require('ol');
/**
* Provides information for an image inside an atlas manager.
* `offsetX` and `offsetY` is the position of the image inside
* the atlas image `image`.
* `hitOffsetX` and `hitOffsetY` ist the position of the hit-detection image
* inside the hit-detection atlas image `hitImage` (only when a hit-detection
* image was created for this image).
* @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement,
* hitOffsetX: number, hitOffsetY: number, hitImage: HTMLCanvasElement}}
*/
ol.style.AtlasManagerInfo;
/**
* Manages the creation of image atlases.
*
* Images added to this manager will be inserted into an atlas, which
* will be used for rendering.
* The `size` given in the constructor is the size for the first
* atlas. After that, when new atlases are created, they will have
* twice the size as the latest atlas (until `maxSize` is reached).
*
* If an application uses many images or very large images, it is recommended
* to set a higher `size` value to avoid the creation of too many atlases.
*
* @constructor
* @struct
* @api
* @param {olx.style.AtlasManagerOptions=} opt_options Options.
*/
ol.style.AtlasManager = function(opt_options) {
var options = goog.isDef(opt_options) ? opt_options : {};
/**
* The size in pixels of the latest atlas image.
* @private
* @type {number}
*/
this.currentSize_ = goog.isDef(options.initialSize) ?
options.initialSize : ol.INITIAL_ATLAS_SIZE;
/**
* The maximum size in pixels of atlas images.
* @private
* @type {number}
*/
this.maxSize_ = goog.isDef(options.maxSize) ?
options.maxSize : ol.MAX_ATLAS_SIZE != -1 ?
ol.MAX_ATLAS_SIZE : goog.isDef(ol.WEBGL_MAX_TEXTURE_SIZE) ?
ol.WEBGL_MAX_TEXTURE_SIZE : 2048;
/**
* The size in pixels between images.
* @private
* @type {number}
*/
this.space_ = goog.isDef(options.space) ? options.space : 1;
/**
* @private
* @type {Array.<ol.style.Atlas>}
*/
this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)];
/**
* The size in pixels of the latest atlas image for hit-detection images.
* @private
* @type {number}
*/
this.currentHitSize_ = this.currentSize_;
/**
* @private
* @type {Array.<ol.style.Atlas>}
*/
this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
};
/**
* @param {string} id The identifier of the entry to check.
* @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
* entry, or `null` if the entry is not part of the atlas manager.
*/
ol.style.AtlasManager.prototype.getInfo = function(id) {
/** @type {?ol.style.AtlasInfo} */
var info = this.getInfo_(this.atlases_, id);
if (goog.isNull(info)) {
return null;
}
/** @type {?ol.style.AtlasInfo} */
var hitInfo = this.getInfo_(this.hitAtlases_, id);
return this.mergeInfos_(info, hitInfo);
};
/**
* @private
* @param {Array.<ol.style.Atlas>} atlases The atlases to search.
* @param {string} id The identifier of the entry to check.
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
* or `null` if the entry is not part of the atlases.
*/
ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) {
var atlas, info, i, ii;
for (i = 0, ii = atlases.length; i < ii; ++i) {
atlas = atlases[i];
info = atlas.get(id);
if (!goog.isNull(info)) {
return info;
}
}
return null;
};
/**
* @private
* @param {ol.style.AtlasInfo} info The info for the real image.
* @param {?ol.style.AtlasInfo} hitInfo The info for the hit-detection
* image.
* @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
* entry, or `null` if the entry is not part of the atlases.
*/
ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
return /** @type {ol.style.AtlasManagerInfo} */ ({
offsetX: info.offsetX,
offsetY: info.offsetY,
image: info.image,
hitOffsetX: goog.isNull(hitInfo) ? undefined : hitInfo.offsetX,
hitOffsetY: goog.isNull(hitInfo) ? undefined : hitInfo.offsetY,
hitImage: goog.isNull(hitInfo) ? undefined : hitInfo.image
});
};
/**
* Add an image to the atlas manager.
*
* If an entry for the given id already exists, the entry will
* be overridden (but the space on the atlas graphic will not be freed).
*
* If `renderHitCallback` is provided, the image (or the hit-detection version
* of the image) will be rendered into a separate hit-detection atlas image.
*
* @param {string} id The identifier of the entry to add.
* @param {number} width The width.
* @param {number} height The height.
* @param {function(CanvasRenderingContext2D, number, number)} renderCallback
* Called to render the new image onto an atlas image.
* @param {function(CanvasRenderingContext2D, number, number)=}
* opt_renderHitCallback Called to render a hit-detection image onto a hit
* detection atlas image.
* @param {Object=} opt_this Value to use as `this` when executing
* `renderCallback` and `renderHitCallback`.
* @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
* entry, or `null` if the image is too big.
*/
ol.style.AtlasManager.prototype.add =
function(id, width, height,
renderCallback, opt_renderHitCallback, opt_this) {
if (width + this.space_ > this.maxSize_ ||
height + this.space_ > this.maxSize_) {
return null;
}
/** @type {?ol.style.AtlasInfo} */
var info = this.add_(false,
id, width, height, renderCallback, opt_this);
if (goog.isNull(info)) {
return null;
}
/** @type {?ol.style.AtlasInfo} */
var hitInfo = null;
if (goog.isDef(opt_renderHitCallback)) {
hitInfo = this.add_(true,
id, width, height, opt_renderHitCallback, opt_this);
}
return this.mergeInfos_(info, hitInfo);
};
/**
* @private
* @param {boolean} isHitAtlas If the hit-detection atlases are used.
* @param {string} id The identifier of the entry to add.
* @param {number} width The width.
* @param {number} height The height.
* @param {function(CanvasRenderingContext2D, number, number)} renderCallback
* Called to render the new image onto an atlas image.
* @param {Object=} opt_this Value to use as `this` when executing
* `renderCallback` and `renderHitCallback`.
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
* or `null` if the image is too big.
*/
ol.style.AtlasManager.prototype.add_ =
function(isHitAtlas, id, width, height,
renderCallback, opt_this) {
var atlases = (isHitAtlas) ? this.hitAtlases_ : this.atlases_;
var atlas, info, i, ii;
for (i = 0, ii = atlases.length; i < ii; ++i) {
atlas = atlases[i];
info = atlas.add(id, width, height, renderCallback, opt_this);
if (!goog.isNull(info)) {
return info;
} else if (goog.isNull(info) && i === ii - 1) {
// the entry could not be added to one of the existing atlases,
// create a new atlas that is twice as big and try to add to this one.
var size;
if (isHitAtlas) {
size = Math.min(this.currentHitSize_ * 2, this.maxSize_);
this.currentHitSize_ = size;
} else {
size = Math.min(this.currentSize_ * 2, this.maxSize_);
this.currentSize_ = size;
}
atlas = new ol.style.Atlas(size, this.space_);
atlases.push(atlas);
// run the loop another time
++ii;
}
}
goog.asserts.fail();
};
/**
* Provides information for an image inside an atlas.
* `offsetX` and `offsetY` are the position of the image inside
* the atlas image `image`.
* @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement}}
*/
ol.style.AtlasInfo;
/**
* This class facilitates the creation of image atlases.
*
* Images added to an atlas will be rendered onto a single
* atlas canvas. The distribution of images on the canvas is
* managed with the bin packing algorithm described in:
* http://www.blackpawn.com/texts/lightmaps/
*
* @constructor
* @struct
* @param {number} size The size in pixels of the sprite image.
* @param {number} space The space in pixels between images.
* Because texture coordinates are float values, the edges of
* images might not be completely correct (in a way that the
* edges overlap when being rendered). To avoid this we add a
* padding around each image.
*/
ol.style.Atlas = function(size, space) {
/**
* @private
* @type {number}
*/
this.space_ = space;
/**
* @private
* @type {Array.<ol.style.Atlas.Block>}
*/
this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];
/**
* @private
* @type {Object.<string, ol.style.AtlasInfo>}
*/
this.entries_ = {};
/**
* @private
* @type {HTMLCanvasElement}
*/
this.canvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
this.canvas_.width = size;
this.canvas_.height = size;
/**
* @private
* @type {CanvasRenderingContext2D}
*/
this.context_ = /** @type {CanvasRenderingContext2D} */
(this.canvas_.getContext('2d'));
};
/**
* @param {string} id The identifier of the entry to check.
* @return {?ol.style.AtlasInfo}
*/
ol.style.Atlas.prototype.get = function(id) {
return /** @type {?ol.style.AtlasInfo} */ (
goog.object.get(this.entries_, id, null));
};
/**
* @param {string} id The identifier of the entry to add.
* @param {number} width The width.
* @param {number} height The height.
* @param {function(CanvasRenderingContext2D, number, number)} renderCallback
* Called to render the new image onto an atlas image.
* @param {Object=} opt_this Value to use as `this` when executing
* `renderCallback`.
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry.
*/
ol.style.Atlas.prototype.add =
function(id, width, height, renderCallback, opt_this) {
var block, i, ii;
for (i = 0, ii = this.emptyBlocks_.length; i < ii; ++i) {
block = this.emptyBlocks_[i];
if (block.width >= width + this.space_ &&
block.height >= height + this.space_) {
// we found a block that is big enough for our entry
var entry = {
offsetX: block.x + this.space_,
offsetY: block.y + this.space_,
image: this.canvas_
};
this.entries_[id] = entry;
// render the image on the atlas image
renderCallback.call(opt_this, this.context_,
block.x + this.space_, block.y + this.space_);
// split the block after the insertion, either horizontally or vertically
this.split_(i, block, width + this.space_, height + this.space_);
return entry;
}
}
// there is no space for the new entry in this atlas
return null;
};
/**
* @private
* @param {number} index The index of the block.
* @param {ol.style.Atlas.Block} block The block to split.
* @param {number} width The width of the entry to insert.
* @param {number} height The height of the entry to insert.
*/
ol.style.Atlas.prototype.split_ =
function(index, block, width, height) {
var deltaWidth = block.width - width;
var deltaHeight = block.height - height;
/** @type {ol.style.Atlas.Block} */
var newBlock1;
/** @type {ol.style.Atlas.Block} */
var newBlock2;
if (deltaWidth > deltaHeight) {
// split vertically
// block right of the inserted entry
newBlock1 = {
x: block.x + width,
y: block.y,
width: block.width - width,
height: block.height
};
// block below the inserted entry
newBlock2 = {
x: block.x,
y: block.y + height,
width: width,
height: block.height - height
};
this.updateBlocks_(index, newBlock1, newBlock2);
} else {
// split horizontally
// block right of the inserted entry
newBlock1 = {
x: block.x + width,
y: block.y,
width: block.width - width,
height: height
};
// block below the inserted entry
newBlock2 = {
x: block.x,
y: block.y + height,
width: block.width,
height: block.height - height
};
this.updateBlocks_(index, newBlock1, newBlock2);
}
};
/**
* Remove the old block and insert new blocks at the same array position.
* The new blocks are inserted at the same position, so that splitted
* blocks (that are potentially smaller) are filled first.
* @private
* @param {number} index The index of the block to remove.
* @param {ol.style.Atlas.Block} newBlock1 The 1st block to add.
* @param {ol.style.Atlas.Block} newBlock2 The 2nd block to add.
*/
ol.style.Atlas.prototype.updateBlocks_ =
function(index, newBlock1, newBlock2) {
var args = [index, 1];
if (newBlock1.width > 0 && newBlock1.height > 0) {
args.push(newBlock1);
}
if (newBlock2.width > 0 && newBlock2.height > 0) {
args.push(newBlock2);
}
this.emptyBlocks_.splice.apply(this.emptyBlocks_, args);
};
/**
* @typedef {{x: number, y: number, width: number, height: number}}
*/
ol.style.Atlas.Block;

View File

@@ -1,10 +1,12 @@
goog.provide('ol.style.Circle');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('ol.color');
goog.require('ol.has');
goog.require('ol.render.canvas');
goog.require('ol.structs.IHasChecksum');
goog.require('ol.style.Fill');
goog.require('ol.style.Image');
goog.require('ol.style.ImageState');
@@ -19,18 +21,24 @@ goog.require('ol.style.Stroke');
* @constructor
* @param {olx.style.CircleOptions=} opt_options Options.
* @extends {ol.style.Image}
* @implements {ol.structs.IHasChecksum}
* @api
*/
ol.style.Circle = function(opt_options) {
var options = goog.isDef(opt_options) ? opt_options : {};
/**
* @private
* @type {Array.<string>}
*/
this.checksums_ = null;
/**
* @private
* @type {HTMLCanvasElement}
*/
this.canvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
this.canvas_ = null;
/**
* @private
@@ -46,9 +54,9 @@ ol.style.Circle = function(opt_options) {
/**
* @private
* @type {Array.<number>}
* @type {ol.style.Stroke}
*/
this.origin_ = [0, 0];
this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
/**
* @private
@@ -58,23 +66,41 @@ ol.style.Circle = function(opt_options) {
/**
* @private
* @type {ol.style.Stroke}
* @type {Array.<number>}
*/
this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
var size = this.render_();
this.origin_ = [0, 0];
/**
* @private
* @type {Array.<number>}
*/
this.anchor_ = [size / 2, size / 2];
this.hitDetectionOrigin_ = [0, 0];
/**
* @private
* @type {Array.<number>}
*/
this.anchor_ = null;
/**
* @private
* @type {ol.Size}
*/
this.size_ = [size, size];
this.size_ = null;
/**
* @private
* @type {ol.Size}
*/
this.imageSize_ = null;
/**
* @private
* @type {ol.Size}
*/
this.hitDetectionImageSize_ = null;
this.render_(options.atlasManager);
/**
* @type {boolean}
@@ -138,6 +164,22 @@ ol.style.Circle.prototype.getImageState = function() {
};
/**
* @inheritDoc
*/
ol.style.Circle.prototype.getImageSize = function() {
return this.imageSize_;
};
/**
* @inheritDoc
*/
ol.style.Circle.prototype.getHitDetectionImageSize = function() {
return this.hitDetectionImageSize_;
};
/**
* @inheritDoc
* @api
@@ -147,6 +189,14 @@ ol.style.Circle.prototype.getOrigin = function() {
};
/**
* @inheritDoc
*/
ol.style.Circle.prototype.getHitDetectionOrigin = function() {
return this.hitDetectionOrigin_;
};
/**
* @return {number} Radius.
* @api
@@ -193,79 +243,216 @@ ol.style.Circle.prototype.unlistenImageChange = goog.nullFunction;
/**
* @private
* @return {number} Size.
* @typedef {{strokeStyle: (string|undefined), strokeWidth: number,
* size: number, lineDash: Array.<number>}}
*/
ol.style.Circle.prototype.render_ = function() {
var canvas = this.canvas_;
var strokeStyle, strokeWidth, lineDash;
ol.style.Circle.RenderOptions;
if (goog.isNull(this.stroke_)) {
strokeWidth = 0;
} else {
/**
* @private
* @param {ol.style.AtlasManager|undefined} atlasManager
*/
ol.style.Circle.prototype.render_ = function(atlasManager) {
var imageSize;
var lineDash = null;
var strokeStyle;
var strokeWidth = 0;
if (!goog.isNull(this.stroke_)) {
strokeStyle = ol.color.asString(this.stroke_.getColor());
strokeWidth = this.stroke_.getWidth();
if (!goog.isDef(strokeWidth)) {
strokeWidth = ol.render.canvas.defaultLineWidth;
}
lineDash = this.stroke_.getLineDash();
if (!ol.has.CANVAS_LINE_DASH) {
lineDash = null;
}
}
var size = 2 * (this.radius_ + strokeWidth) + 1;
// draw the circle on the canvas
/** @type {ol.style.Circle.RenderOptions} */
var renderOptions = {
strokeStyle: strokeStyle,
strokeWidth: strokeWidth,
size: size,
lineDash: lineDash
};
canvas.height = size;
canvas.width = size;
if (!goog.isDef(atlasManager)) {
// no atlas manager is used, create a new canvas
this.canvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
this.canvas_.height = size;
this.canvas_.width = size;
// canvas.width and height are rounded to the closest integer
size = canvas.width;
// canvas.width and height are rounded to the closest integer
size = this.canvas_.width;
imageSize = size;
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
context.arc(size / 2, size / 2, this.radius_, 0, 2 * Math.PI, true);
// draw the circle on the canvas
var context = /** @type {CanvasRenderingContext2D} */
(this.canvas_.getContext('2d'));
this.draw_(renderOptions, context, 0, 0);
this.createHitDetectionCanvas_(renderOptions);
} else {
// an atlas manager is used, add the symbol to an atlas
size = Math.round(size);
var hasCustomHitDetectionImage = goog.isNull(this.fill_);
var renderHitDetectionCallback;
if (hasCustomHitDetectionImage) {
// render the hit-detection image into a separate atlas image
renderHitDetectionCallback =
goog.bind(this.drawHitDetectionCanvas_, this, renderOptions);
}
var id = this.getChecksum();
var info = atlasManager.add(
id, size, size, goog.bind(this.draw_, this, renderOptions),
renderHitDetectionCallback);
goog.asserts.assert(info !== null, 'circle radius is too large');
this.canvas_ = info.image;
this.origin_ = [info.offsetX, info.offsetY];
imageSize = info.image.width;
if (hasCustomHitDetectionImage) {
this.hitDetectionCanvas_ = info.hitImage;
this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY];
this.hitDetectionImageSize_ =
[info.hitImage.width, info.hitImage.height];
} else {
this.hitDetectionCanvas_ = this.canvas_;
this.hitDetectionOrigin_ = this.origin_;
this.hitDetectionImageSize_ = [imageSize, imageSize];
}
}
this.anchor_ = [size / 2, size / 2];
this.size_ = [size, size];
this.imageSize_ = [imageSize, imageSize];
};
/**
* @private
* @param {ol.style.Circle.RenderOptions} renderOptions
* @param {CanvasRenderingContext2D} context
* @param {number} x The origin for the symbol (x).
* @param {number} y The origin for the symbol (y).
*/
ol.style.Circle.prototype.draw_ = function(renderOptions, context, x, y) {
// reset transform
context.setTransform(1, 0, 0, 1, 0, 0);
// then move to (x, y)
context.translate(x, y);
context.beginPath();
context.arc(
renderOptions.size / 2, renderOptions.size / 2,
this.radius_, 0, 2 * Math.PI, true);
if (!goog.isNull(this.fill_)) {
context.fillStyle = ol.color.asString(this.fill_.getColor());
context.fill();
}
if (!goog.isNull(this.stroke_)) {
context.strokeStyle = strokeStyle;
lineDash = this.stroke_.getLineDash();
if (ol.has.CANVAS_LINE_DASH && !goog.isNull(lineDash)) {
context.setLineDash(lineDash);
context.strokeStyle = renderOptions.strokeStyle;
context.lineWidth = renderOptions.strokeWidth;
if (!goog.isNull(renderOptions.lineDash)) {
context.setLineDash(renderOptions.lineDash);
}
context.lineWidth = strokeWidth;
context.stroke();
}
context.closePath();
};
// deal with the hit detection canvas
/**
* @private
* @param {ol.style.Circle.RenderOptions} renderOptions
*/
ol.style.Circle.prototype.createHitDetectionCanvas_ = function(renderOptions) {
this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
if (!goog.isNull(this.fill_)) {
this.hitDetectionCanvas_ = canvas;
} else {
this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
canvas = this.hitDetectionCanvas_;
canvas.height = size;
canvas.width = size;
context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
context.arc(size / 2, size / 2, this.radius_, 0, 2 * Math.PI, true);
context.fillStyle = ol.render.canvas.defaultFillStyle;
context.fill();
if (!goog.isNull(this.stroke_)) {
context.strokeStyle = strokeStyle;
lineDash = this.stroke_.getLineDash();
if (ol.has.CANVAS_LINE_DASH && !goog.isNull(lineDash)) {
context.setLineDash(lineDash);
}
context.lineWidth = strokeWidth;
context.stroke();
}
this.hitDetectionCanvas_ = this.canvas_;
return;
}
return size;
// if no fill style is set, create an extra hit-detection image with a
// default fill style
this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
var canvas = this.hitDetectionCanvas_;
canvas.height = renderOptions.size;
canvas.width = renderOptions.size;
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
};
/**
* @private
* @param {ol.style.Circle.RenderOptions} renderOptions
* @param {CanvasRenderingContext2D} context
* @param {number} x The origin for the symbol (x).
* @param {number} y The origin for the symbol (y).
*/
ol.style.Circle.prototype.drawHitDetectionCanvas_ =
function(renderOptions, context, x, y) {
// reset transform
context.setTransform(1, 0, 0, 1, 0, 0);
// then move to (x, y)
context.translate(x, y);
context.beginPath();
context.arc(
renderOptions.size / 2, renderOptions.size / 2,
this.radius_, 0, 2 * Math.PI, true);
context.fillStyle = ol.render.canvas.defaultFillStyle;
context.fill();
if (!goog.isNull(this.stroke_)) {
context.strokeStyle = renderOptions.strokeStyle;
context.lineWidth = renderOptions.strokeWidth;
if (!goog.isNull(renderOptions.lineDash)) {
context.setLineDash(renderOptions.lineDash);
}
context.stroke();
}
context.closePath();
};
/**
* @inheritDoc
*/
ol.style.Circle.prototype.getChecksum = function() {
var strokeChecksum = !goog.isNull(this.stroke_) ?
this.stroke_.getChecksum() : '-';
var fillChecksum = !goog.isNull(this.fill_) ?
this.fill_.getChecksum() : '-';
var recalculate = goog.isNull(this.checksums_) ||
(strokeChecksum != this.checksums_[1] ||
fillChecksum != this.checksums_[2] ||
this.radius_ != this.checksums_[3]);
if (recalculate) {
var checksum = 'c' + strokeChecksum + fillChecksum +
(goog.isDef(this.radius_) ? this.radius_.toString() : '-');
this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_];
}
return this.checksums_[0];
};

View File

@@ -1,5 +1,8 @@
goog.provide('ol.style.Fill');
goog.require('ol.color');
goog.require('ol.structs.IHasChecksum');
/**
@@ -8,6 +11,7 @@ goog.provide('ol.style.Fill');
*
* @constructor
* @param {olx.style.FillOptions=} opt_options Options.
* @implements {ol.structs.IHasChecksum}
* @api
*/
ol.style.Fill = function(opt_options) {
@@ -19,6 +23,12 @@ ol.style.Fill = function(opt_options) {
* @type {ol.Color|string}
*/
this.color_ = goog.isDef(options.color) ? options.color : null;
/**
* @private
* @type {string|undefined}
*/
this.checksum_ = undefined;
};
@@ -39,4 +49,18 @@ ol.style.Fill.prototype.getColor = function() {
*/
ol.style.Fill.prototype.setColor = function(color) {
this.color_ = color;
this.checksum_ = undefined;
};
/**
* @inheritDoc
*/
ol.style.Fill.prototype.getChecksum = function() {
if (!goog.isDef(this.checksum_)) {
this.checksum_ = 'f' + (!goog.isNull(this.color_) ?
ol.color.asString(this.color_) : '-');
}
return this.checksum_;
};

View File

@@ -246,6 +246,14 @@ ol.style.Icon.prototype.getImageSize = function() {
};
/**
* @inheritDoc
*/
ol.style.Icon.prototype.getHitDetectionImageSize = function() {
return this.getImageSize();
};
/**
* @inheritDoc
*/
@@ -293,6 +301,14 @@ ol.style.Icon.prototype.getOrigin = function() {
};
/**
* @inheritDoc
*/
ol.style.Icon.prototype.getHitDetectionOrigin = function() {
return this.getOrigin();
};
/**
* @return {string|undefined} Image src.
* @api

View File

@@ -126,12 +126,6 @@ ol.style.Image.prototype.getAnchor = goog.abstractMethod;
ol.style.Image.prototype.getImage = goog.abstractMethod;
/**
* @return {ol.style.ImageState} Image state.
*/
ol.style.Image.prototype.getImageState = goog.abstractMethod;
/**
* @param {number} pixelRatio Pixel ratio.
* @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
@@ -139,6 +133,24 @@ ol.style.Image.prototype.getImageState = goog.abstractMethod;
ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod;
/**
* @return {ol.style.ImageState} Image state.
*/
ol.style.Image.prototype.getImageState = goog.abstractMethod;
/**
* @return {ol.Size} Image size.
*/
ol.style.Image.prototype.getImageSize = goog.abstractMethod;
/**
* @return {ol.Size} Size of the hit-detection image.
*/
ol.style.Image.prototype.getHitDetectionImageSize = goog.abstractMethod;
/**
* @function
* @return {Array.<number>} Origin.
@@ -146,6 +158,13 @@ ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod;
ol.style.Image.prototype.getOrigin = goog.abstractMethod;
/**
* @function
* @return {Array.<number>} Origin for the hit-detection image.
*/
ol.style.Image.prototype.getHitDetectionOrigin = goog.abstractMethod;
/**
* @function
* @return {ol.Size} Size.

View File

@@ -22,18 +22,24 @@ goog.require('ol.style.Stroke');
* @constructor
* @param {olx.style.RegularShapeOptions=} opt_options Options.
* @extends {ol.style.Image}
* @implements {ol.structs.IHasChecksum}
* @api
*/
ol.style.RegularShape = function(opt_options) {
var options = goog.isDef(opt_options) ? opt_options : {};
/**
* @private
* @type {Array.<string>}
*/
this.checksums_ = null;
/**
* @private
* @type {HTMLCanvasElement}
*/
this.canvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
this.canvas_ = null;
/**
* @private
@@ -53,6 +59,12 @@ ol.style.RegularShape = function(opt_options) {
*/
this.origin_ = [0, 0];
/**
* @private
* @type {Array.<number>}
*/
this.hitDetectionOrigin_ = [0, 0];
/**
* @private
* @type {number}
@@ -88,19 +100,31 @@ ol.style.RegularShape = function(opt_options) {
*/
this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
var size = this.render_();
/**
* @private
* @type {Array.<number>}
*/
this.anchor_ = [size / 2, size / 2];
this.anchor_ = null;
/**
* @private
* @type {ol.Size}
*/
this.size_ = [size, size];
this.size_ = null;
/**
* @private
* @type {ol.Size}
*/
this.imageSize_ = null;
/**
* @private
* @type {ol.Size}
*/
this.hitDetectionImageSize_ = null;
this.render_(options.atlasManager);
/**
* @type {boolean}
@@ -155,6 +179,22 @@ ol.style.RegularShape.prototype.getImage = function(pixelRatio) {
};
/**
* @inheritDoc
*/
ol.style.RegularShape.prototype.getImageSize = function() {
return this.imageSize_;
};
/**
* @inheritDoc
*/
ol.style.RegularShape.prototype.getHitDetectionImageSize = function() {
return this.hitDetectionImageSize_;
};
/**
* @inheritDoc
*/
@@ -172,6 +212,14 @@ ol.style.RegularShape.prototype.getOrigin = function() {
};
/**
* @inheritDoc
*/
ol.style.RegularShape.prototype.getHitDetectionOrigin = function() {
return this.hitDetectionOrigin_;
};
/**
* @return {number} Radius.
* @api
@@ -227,36 +275,116 @@ ol.style.RegularShape.prototype.unlistenImageChange = goog.nullFunction;
/**
* @private
* @return {number} Size.
* @typedef {{strokeStyle: (string|undefined), strokeWidth: number,
* size: number, lineDash: Array.<number>}}
*/
ol.style.RegularShape.prototype.render_ = function() {
var canvas = this.canvas_;
var strokeStyle, strokeWidth, lineDash;
ol.style.RegularShape.RenderOptions;
if (goog.isNull(this.stroke_)) {
strokeWidth = 0;
} else {
/**
* @private
* @param {ol.style.AtlasManager|undefined} atlasManager
*/
ol.style.RegularShape.prototype.render_ = function(atlasManager) {
var imageSize;
var lineDash = null;
var strokeStyle;
var strokeWidth = 0;
if (!goog.isNull(this.stroke_)) {
strokeStyle = ol.color.asString(this.stroke_.getColor());
strokeWidth = this.stroke_.getWidth();
if (!goog.isDef(strokeWidth)) {
strokeWidth = ol.render.canvas.defaultLineWidth;
}
lineDash = this.stroke_.getLineDash();
if (!ol.has.CANVAS_LINE_DASH) {
lineDash = null;
}
}
var size = 2 * (this.radius_ + strokeWidth) + 1;
// draw the regular shape on the canvas
/** @type {ol.style.RegularShape.RenderOptions} */
var renderOptions = {
strokeStyle: strokeStyle,
strokeWidth: strokeWidth,
size: size,
lineDash: lineDash
};
canvas.height = size;
canvas.width = size;
if (!goog.isDef(atlasManager)) {
// no atlas manager is used, create a new canvas
this.canvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
// canvas.width and height are rounded to the closest integer
size = canvas.width;
this.canvas_.height = size;
this.canvas_.width = size;
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
// canvas.width and height are rounded to the closest integer
size = this.canvas_.width;
imageSize = size;
var context = /** @type {CanvasRenderingContext2D} */
(this.canvas_.getContext('2d'));
this.draw_(renderOptions, context, 0, 0);
this.createHitDetectionCanvas_(renderOptions);
} else {
// an atlas manager is used, add the symbol to an atlas
size = Math.round(size);
var hasCustomHitDetectionImage = goog.isNull(this.fill_);
var renderHitDetectionCallback;
if (hasCustomHitDetectionImage) {
// render the hit-detection image into a separate atlas image
renderHitDetectionCallback =
goog.bind(this.drawHitDetectionCanvas_, this, renderOptions);
}
var id = this.getChecksum();
var info = atlasManager.add(
id, size, size, goog.bind(this.draw_, this, renderOptions),
renderHitDetectionCallback);
goog.asserts.assert(!goog.isNull(info), 'shape size is too large');
this.canvas_ = info.image;
this.origin_ = [info.offsetX, info.offsetY];
imageSize = info.image.width;
if (hasCustomHitDetectionImage) {
this.hitDetectionCanvas_ = info.hitImage;
this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY];
this.hitDetectionImageSize_ =
[info.hitImage.width, info.hitImage.height];
} else {
this.hitDetectionCanvas_ = this.canvas_;
this.hitDetectionOrigin_ = this.origin_;
this.hitDetectionImageSize_ = [imageSize, imageSize];
}
}
this.anchor_ = [size / 2, size / 2];
this.size_ = [size, size];
this.imageSize_ = [imageSize, imageSize];
};
/**
* @private
* @param {ol.style.Circle.RenderOptions} renderOptions
* @param {CanvasRenderingContext2D} context
* @param {number} x The origin for the symbol (x).
* @param {number} y The origin for the symbol (y).
*/
ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) {
var i, angle0, radiusC;
// reset transform
context.setTransform(1, 0, 0, 1, 0, 0);
// then move to (x, y)
context.translate(x, y);
context.beginPath();
if (this.radius2_ !== this.radius_) {
this.points_ = 2 * this.points_;
@@ -264,8 +392,8 @@ ol.style.RegularShape.prototype.render_ = function() {
for (i = 0; i <= this.points_; i++) {
angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
context.lineTo(size / 2 + radiusC * Math.cos(angle0),
size / 2 + radiusC * Math.sin(angle0));
context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
renderOptions.size / 2 + radiusC * Math.sin(angle0));
}
if (!goog.isNull(this.fill_)) {
@@ -273,52 +401,111 @@ ol.style.RegularShape.prototype.render_ = function() {
context.fill();
}
if (!goog.isNull(this.stroke_)) {
context.strokeStyle = strokeStyle;
lineDash = this.stroke_.getLineDash();
if (ol.has.CANVAS_LINE_DASH && !goog.isNull(lineDash)) {
context.setLineDash(lineDash);
context.strokeStyle = renderOptions.strokeStyle;
context.lineWidth = renderOptions.strokeWidth;
if (!goog.isNull(renderOptions.lineDash)) {
context.setLineDash(renderOptions.lineDash);
}
context.lineWidth = strokeWidth;
context.stroke();
}
context.closePath();
};
// deal with the hit detection canvas
/**
* @private
* @param {ol.style.RegularShape.RenderOptions} renderOptions
*/
ol.style.RegularShape.prototype.createHitDetectionCanvas_ =
function(renderOptions) {
this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
if (!goog.isNull(this.fill_)) {
this.hitDetectionCanvas_ = canvas;
} else {
this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
canvas = this.hitDetectionCanvas_;
canvas.height = size;
canvas.width = size;
context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
context.beginPath();
if (this.radius2_ !== this.radius_) {
this.points_ = 2 * this.points_;
}
for (i = 0; i <= this.points_; i++) {
angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
context.lineTo(size / 2 + radiusC * Math.cos(angle0),
size / 2 + radiusC * Math.sin(angle0));
}
context.fillStyle = ol.render.canvas.defaultFillStyle;
context.fill();
if (!goog.isNull(this.stroke_)) {
context.strokeStyle = strokeStyle;
lineDash = this.stroke_.getLineDash();
if (ol.has.CANVAS_LINE_DASH && !goog.isNull(lineDash)) {
context.setLineDash(lineDash);
}
context.lineWidth = strokeWidth;
context.stroke();
}
this.hitDetectionCanvas_ = this.canvas_;
return;
}
return size;
// if no fill style is set, create an extra hit-detection image with a
// default fill style
this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
var canvas = this.hitDetectionCanvas_;
canvas.height = renderOptions.size;
canvas.width = renderOptions.size;
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
};
/**
* @private
* @param {ol.style.RegularShape.RenderOptions} renderOptions
* @param {CanvasRenderingContext2D} context
* @param {number} x The origin for the symbol (x).
* @param {number} y The origin for the symbol (y).
*/
ol.style.RegularShape.prototype.drawHitDetectionCanvas_ =
function(renderOptions, context, x, y) {
// reset transform
context.setTransform(1, 0, 0, 1, 0, 0);
// then move to (x, y)
context.translate(x, y);
context.beginPath();
if (this.radius2_ !== this.radius_) {
this.points_ = 2 * this.points_;
}
var i, radiusC, angle0;
for (i = 0; i <= this.points_; i++) {
angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
renderOptions.size / 2 + radiusC * Math.sin(angle0));
}
context.fillStyle = ol.render.canvas.defaultFillStyle;
context.fill();
if (!goog.isNull(this.stroke_)) {
context.strokeStyle = renderOptions.strokeStyle;
context.lineWidth = renderOptions.strokeWidth;
if (!goog.isNull(renderOptions.lineDash)) {
context.setLineDash(renderOptions.lineDash);
}
context.stroke();
}
context.closePath();
};
/**
* @inheritDoc
*/
ol.style.RegularShape.prototype.getChecksum = function() {
var strokeChecksum = !goog.isNull(this.stroke_) ?
this.stroke_.getChecksum() : '-';
var fillChecksum = !goog.isNull(this.fill_) ?
this.fill_.getChecksum() : '-';
var recalculate = goog.isNull(this.checksums_) ||
(strokeChecksum != this.checksums_[1] ||
fillChecksum != this.checksums_[2] ||
this.radius_ != this.checksums_[3] ||
this.radius2_ != this.checksums_[4] ||
this.angle_ != this.checksums_[5] ||
this.points_ != this.checksums_[6]);
if (recalculate) {
var checksum = 'r' + strokeChecksum + fillChecksum +
(goog.isDef(this.radius_) ? this.radius_.toString() : '-') +
(goog.isDef(this.radius2_) ? this.radius2_.toString() : '-') +
(goog.isDef(this.angle_) ? this.angle_.toString() : '-') +
(goog.isDef(this.points_) ? this.points_.toString() : '-');
this.checksums_ = [checksum, strokeChecksum, fillChecksum,
this.radius_, this.radius2_, this.angle_, this.points_];
}
return this.checksums_[0];
};

View File

@@ -1,5 +1,10 @@
goog.provide('ol.style.Stroke');
goog.require('goog.crypt');
goog.require('goog.crypt.Md5');
goog.require('ol.color');
goog.require('ol.structs.IHasChecksum');
/**
@@ -11,6 +16,7 @@ goog.provide('ol.style.Stroke');
*
* @constructor
* @param {olx.style.StrokeOptions=} opt_options Options.
* @implements {ol.structs.IHasChecksum}
* @api
*/
ol.style.Stroke = function(opt_options) {
@@ -52,6 +58,12 @@ ol.style.Stroke = function(opt_options) {
* @type {number|undefined}
*/
this.width_ = options.width;
/**
* @private
* @type {string|undefined}
*/
this.checksum_ = undefined;
};
@@ -117,6 +129,7 @@ ol.style.Stroke.prototype.getWidth = function() {
*/
ol.style.Stroke.prototype.setColor = function(color) {
this.color_ = color;
this.checksum_ = undefined;
};
@@ -128,6 +141,7 @@ ol.style.Stroke.prototype.setColor = function(color) {
*/
ol.style.Stroke.prototype.setLineCap = function(lineCap) {
this.lineCap_ = lineCap;
this.checksum_ = undefined;
};
@@ -139,6 +153,7 @@ ol.style.Stroke.prototype.setLineCap = function(lineCap) {
*/
ol.style.Stroke.prototype.setLineDash = function(lineDash) {
this.lineDash_ = lineDash;
this.checksum_ = undefined;
};
@@ -150,6 +165,7 @@ ol.style.Stroke.prototype.setLineDash = function(lineDash) {
*/
ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
this.lineJoin_ = lineJoin;
this.checksum_ = undefined;
};
@@ -161,6 +177,7 @@ ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
*/
ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
this.miterLimit_ = miterLimit;
this.checksum_ = undefined;
};
@@ -172,4 +189,33 @@ ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
*/
ol.style.Stroke.prototype.setWidth = function(width) {
this.width_ = width;
this.checksum_ = undefined;
};
/**
* @inheritDoc
*/
ol.style.Stroke.prototype.getChecksum = function() {
if (!goog.isDef(this.checksum_)) {
var raw = 's' +
(!goog.isNull(this.color_) ?
ol.color.asString(this.color_) : '-') + ',' +
(goog.isDef(this.lineCap_) ?
this.lineCap_.toString() : '-') + ',' +
(!goog.isNull(this.lineDash_) ?
this.lineDash_.toString() : '-') + ',' +
(goog.isDef(this.lineJoin_) ?
this.lineJoin_ : '-') + ',' +
(goog.isDef(this.miterLimit_) ?
this.miterLimit_.toString() : '-') + ',' +
(goog.isDef(this.width_) ?
this.width_.toString() : '-');
var md5 = new goog.crypt.Md5();
md5.update(raw);
this.checksum_ = goog.crypt.byteArrayToString(md5.digest());
}
return this.checksum_;
};

56
src/ol/webgl/buffer.js Normal file
View File

@@ -0,0 +1,56 @@
goog.provide('ol.webgl.Buffer');
goog.require('goog.array');
goog.require('goog.webgl');
goog.require('ol');
/**
* @enum {number}
*/
ol.webgl.BufferUsage = {
STATIC_DRAW: goog.webgl.STATIC_DRAW,
STREAM_DRAW: goog.webgl.STREAM_DRAW,
DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW
};
/**
* @constructor
* @param {Array.<number>=} opt_arr Array.
* @param {number=} opt_usage Usage.
* @struct
*/
ol.webgl.Buffer = function(opt_arr, opt_usage) {
/**
* @private
* @type {Array.<number>}
*/
this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
/**
* @private
* @type {number}
*/
this.usage_ = goog.isDef(opt_usage) ?
opt_usage : ol.webgl.BufferUsage.STATIC_DRAW;
};
/**
* @return {Array.<number>} Array.
*/
ol.webgl.Buffer.prototype.getArray = function() {
return this.arr_;
};
/**
* @return {number} Usage.
*/
ol.webgl.Buffer.prototype.getUsage = function() {
return this.usage_;
};

View File

@@ -1,18 +1,18 @@
goog.provide('ol.webgl.Context');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.log');
goog.require('goog.object');
goog.require('ol.structs.Buffer');
goog.require('ol.structs.IntegerSet');
goog.require('ol');
goog.require('ol.webgl.Buffer');
goog.require('ol.webgl.WebGLContextEventType');
/**
* @typedef {{buf: ol.structs.Buffer,
* buffer: WebGLBuffer,
* dirtySet: ol.structs.IntegerSet}}
* @typedef {{buf: ol.webgl.Buffer,
* buffer: WebGLBuffer}}
*/
ol.webgl.BufferCacheEntry;
@@ -66,6 +66,18 @@ ol.webgl.Context = function(canvas, gl) {
*/
this.currentProgram_ = null;
/**
* @type {boolean}
*/
this.hasOESElementIndexUint = goog.array.contains(
ol.WEBGL_EXTENSIONS, 'OES_element_index_uint');
// use the OES_element_index_uint extension if available
if (this.hasOESElementIndexUint) {
var ext = gl.getExtension('OES_element_index_uint');
goog.asserts.assert(!goog.isNull(ext));
}
goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST,
this.handleWebGLContextLost, false, this);
goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED,
@@ -75,8 +87,11 @@ ol.webgl.Context = function(canvas, gl) {
/**
* Just bind the buffer if it's in the cache. Otherwise create
* the WebGL buffer, bind it, populate it, and add an entry to
* the cache.
* @param {number} target Target.
* @param {ol.structs.Buffer} buf Buffer.
* @param {ol.webgl.Buffer} buf Buffer.
*/
ol.webgl.Context.prototype.bindBuffer = function(target, buf) {
var gl = this.getGL();
@@ -85,45 +100,37 @@ ol.webgl.Context.prototype.bindBuffer = function(target, 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);
goog.asserts.assert(target == goog.webgl.ARRAY_BUFFER ||
target == goog.webgl.ELEMENT_ARRAY_BUFFER);
var /** @type {ArrayBufferView} */ arrayBuffer;
if (target == goog.webgl.ARRAY_BUFFER) {
arrayBuffer = new Float32Array(arr);
} else if (target == goog.webgl.ELEMENT_ARRAY_BUFFER) {
arrayBuffer = this.hasOESElementIndexUint ?
new Uint32Array(arr) : new Uint16Array(arr);
} else {
goog.asserts.fail();
}
gl.bufferData(target, arrayBuffer, buf.getUsage());
this.bufferCache_[bufferKey] = {
buf: buf,
buffer: buffer,
dirtySet: dirtySet
buffer: buffer
};
}
};
/**
* @param {ol.structs.Buffer} buf Buffer.
* @param {ol.webgl.Buffer} buf Buffer.
*/
ol.webgl.Context.prototype.deleteBuffer = function(buf) {
var gl = this.getGL();
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);
}
@@ -135,9 +142,6 @@ ol.webgl.Context.prototype.deleteBuffer = function(buf) {
* @inheritDoc
*/
ol.webgl.Context.prototype.disposeInternal = function() {
goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) {
bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet);
});
var gl = this.getGL();
if (!gl.isContextLost()) {
goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) {
@@ -171,6 +175,8 @@ ol.webgl.Context.prototype.getGL = function() {
/**
* Get shader from the cache if it's in the cache. Otherwise, create
* the WebGL shader, compile it, and add entry to cache.
* @param {ol.webgl.Shader} shaderObject Shader object.
* @return {WebGLShader} Shader.
*/
@@ -199,6 +205,9 @@ ol.webgl.Context.prototype.getShader = function(shaderObject) {
/**
* Get the program from the cache if it's in the cache. Otherwise create
* the WebGL program, attach the shaders to it, and add an entry to the
* cache.
* @param {ol.webgl.shader.Fragment} fragmentShaderObject Fragment shader.
* @param {ol.webgl.shader.Vertex} vertexShaderObject Vertex shader.
* @return {WebGLProgram} Program.
@@ -249,6 +258,9 @@ ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
/**
* Just return false if that program is used already. Other use
* that program (call `gl.useProgram`) and make it the "current
* program".
* @param {WebGLProgram} program Program.
* @return {boolean} Changed.
* @api