/*
* Copyright 2003-2006, 2009, 2017, 2018, 2019, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports GriddedDataLayer
*/
define([
'../render/DoubleBufferedFbo',
'../shaders/GridParticleProgram',
'../shaders/GridParticleSimProgram',
'../geom/Location',
'../geom/Sector',
'./TiledImageLayer',
'../util/WWMath',
'../util/WWUtil'
],
function (
DoubleBufferedFbo,
GridParticleProgram,
GridParticleSimProgram,
Location,
Sector,
TiledImageLayer,
WWMath,
WWUtil) {
'use strict';
/**
* Constructs a GriddedDataLayer.
*
* The GriddedDataLayer displays vector GRIB data as moving particles with trails.
* The GRIB data is loaded from a JSON file as outputed by the grib2json library.
* Alternatively apps can load the JSON file themselves and set the gridData property.
*
* @example
* var windLayer = new GriddedDataLayer('Wind', './wind.json');
*
* @example
* var windLayer = new GriddedDataLayer('Wind');
* fetch('./wind.json')
* .then(res => res.json())
* .then(windGribData => windLayer.gridData = windLayer.createGridData(windGribData));
*
* @alias GriddedDataLayer
* @constructor
* @augments TiledImageLayer
* @classdesc A GriddedDataLayer layer for visualising vector data.
* @param {String} displayName This layer's display name.
* @param {String} gridDataUrl An url from which to load the grib data
*/
var GriddedDataLayer = function (displayName, gridDataUrl) {
var numLevels = 16;
var baseCacheKey = 'GriddedDataLayer_' + WWUtil.guid();
var tileWidth = 256;
var tileHeight = 256;
TiledImageLayer.call(this, new Sector(-90, 90, -180, 180), new Location(45, 45), numLevels, 'image/png', baseCacheKey, tileWidth, tileHeight);
this.displayName = displayName || 'GriddedDataLayer';
//Documented in defineProperties below.
this._numParticles = 16384;
//Documented in defineProperties below.
this._colors = {
0.0: '#3288bd',
0.1: '#66c2a5',
0.2: '#abdda4',
0.3: '#e6f598',
0.4: '#fee08b',
0.5: '#fdae61',
0.6: '#f46d43',
1.0: '#d53e4f'
};
/**
* Controls how fast the particle trails fade on each frame.
* A number between [0, 1)
* @type {Number}
* @default 0.996
*/
this.fadeOpacity = 0.996;
/**
* Controls how fast the particles move
* @type {Number}
* @default 0.25
*/
this.speedFactor = 0.25;
/**
* Controls how often the particles move to a random place
* @type {Number}
* @default 0.003
*/
this.dropRate = 0.003;
/**
* Controls the drop rate of a particle relative to individual particle speed.
* @type {Number}
* @default 0.01
*/
this.dropRateMultiplier = 0.01;
/**
* Rendering pauses when the globe is moving and resumes some time after the globe is not moving anymore.
* The time, in miliseconds, after which rendering resumes.
* @type {Number}
* @default 334
*/
this.waitTime = 334;
/**
* The gridData as outputed by the createGridData method.
* @type {Object}
*/
this.gridData = null;
/**
* Reduces the eye altitude by the specified amount (default is 15%).
* By reducing the eye a more precise boundig secor cand be determined by culling tile on the edges of the globe.
* Setting this value to 0 will render all visible tiles.
* @type {Number}
* @default 0.15
*/
this.eyeAltitudeReduction = 0.15;
/**
* Specifies the maxmimum altitude at which visible tile will be culled.
* At altitudes higher than this value all visible tiles are rendered (eyeAltitudeReduction is considred to be 0)
* @type {Number}
* @default 20e6
*/
this.eyeAltitudeReductionTrehshold = 20e6;
/**
* The bounding sector of this layer as computed from the tiles.
* @type {Sector}
* @readonly
*/
this.sector = new Sector(-90, 90, -180, 180);
//Internal use only.
//url for the GRIB JSON file.
this._gridDataUrl = gridDataUrl;
//Internal use only.
//The width and height of the sim texture.
this._simTextureResolution = 128;
//Internal use only.
//The width and height of the ground texture.
this._groundTextureResolution = 2048;
//Internal use only.
//A flag to indicate the GRIB data is currently being retrieved.
this._loadInProgress = false;
//Internal use only.
//A normalised bounding box.
this._bbox = new Float32Array([0, 0, 1, 1]);
//Internal use only.
//The time, in miliseconds, when the last fbo clear happned.
this._lastGroundFboClear = 0;
//Internal use only.
//A flag to indicate that an fbo clear is necessary
this._requiresFboClear = false;
//Internal use only.
//gpuCacheKeys
this._simTextureKey = baseCacheKey + '_simTexture';
this._simTextureKey1 = baseCacheKey + '_simTexture1';
this._simTextureKey2 = baseCacheKey + '_simTexture2';
this._colorsTextureKey = baseCacheKey + '_colorsTexture';
this._gridTextureKey = baseCacheKey + '_gridTexure';
this._groundTextureKey = baseCacheKey + '_groundTexure';
this._groundTextureKey1 = baseCacheKey + '_groundTexure1';
this._groundTextureKey2 = baseCacheKey + '_groundTexure2';
this._particleVboKey = baseCacheKey + '_particleVbo';
this._quadVboKey = baseCacheKey + '_quadVbo';
};
GriddedDataLayer.prototype = Object.create(TiledImageLayer.prototype);
Object.defineProperties(GriddedDataLayer.prototype, {
/**
* The number of particles to render.
* @memberof GriddedDataLayer.prototype
* @type {Number}
* @default 16384
*/
numParticles: {
get: function () {
return this._numParticles;
},
set: function (numParticles) {
this._simTextureResolution = Math.ceil(Math.sqrt(numParticles));
this._numParticles = this._simTextureResolution * this._simTextureResolution;
var baseCacheKey = 'GriddedDataLayer_' + WWUtil.guid();
this._simTextureKey = baseCacheKey + '_simTexture';
this._simTextureKey1 = baseCacheKey + '_simTexture1';
this._simTextureKey2 = baseCacheKey + '_simTexture2';
this._particleVboKey = baseCacheKey + '_particleVbo';
this._requiresFboClear = true;
}
},
/**
* An object describing a color gradient.
* The keys are the color stops (between 0 and 1) and the values are valid CSS colors.
* @memberof GriddedDataLayer.prototype
* @type {Object}
*/
colors: {
get: function () {
return this._colors;
},
set: function (colors) {
this._colors = colors;
var baseCacheKey = 'GriddedDataLayer_' + WWUtil.guid();
this._colorsTextureKey = baseCacheKey + '_colorsTexture';
this._requiresFboClear = true;
}
}
});
// Documented in superclass.
GriddedDataLayer.prototype.doRender = function (dc) {
dc.redrawRequested = true;
if (!dc.terrain) {
return;
}
if (!this.gridData) {
this.loadGridData(this._gridDataUrl);
return;
}
var mvpMatrixChanged = !dc.modelviewProjection.equals(this.lasTtMVP);
if (this.currentTilesInvalid
|| mvpMatrixChanged
|| dc.globeStateKey !== this.lastGlobeStateKey) {
this.currentTilesInvalid = false;
this.assembleTiles(dc);
if (!dc.globe.is2D()) {
this.currentTiles = this.cullTilesHorizon(dc, this.currentTiles);
}
if (mvpMatrixChanged) {
this._requiresFboClear = true;
}
}
this.lasTtMVP.copy(dc.modelviewProjection);
this.lastGlobeStateKey = dc.globeStateKey;
if (this.currentTiles.length > 0) {
if (this._requiresFboClear) {
this.clearGroundFbo(dc);
}
if (Date.now() - this._lastGroundFboClear <= this.waitTime) {
return;
}
try {
this.renderToTexture(dc);
}
finally {
this.restoreGLState(dc);
}
dc.surfaceTileRenderer.renderTiles(dc, [this], this.opacity, dc.surfaceOpacity >= 1);
dc.frameStatistics.incrementImageTileCount(1);
this.inCurrentFrame = true;
}
};
/**
* Renders the grid data as particles with trails to an offsreeen framebuffer.
* @param {DrawContext} dc
*/
GriddedDataLayer.prototype.renderToTexture = function (dc) {
var gl = dc.currentGlContext;
var gpuResourceCache = dc.gpuResourceCache;
var glAllBuffers = gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT;
var groundKey = this._groundTextureKey;
var simKey = this._simTextureKey;
if (dc.globe.offset === 1) {
groundKey = this._groundTextureKey1;
simKey = this._simTextureKey1;
}
else if (dc.globe.offset === -1) {
groundKey = this._groundTextureKey2;
simKey = this._simTextureKey2;
}
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.STENCIL_TEST);
gl.blendFunc(gl.ONE, gl.ZERO);
gl.activeTexture(gl.TEXTURE0);
var gridTexture = gpuResourceCache.resourceForKey(this._gridTextureKey);
if (!gridTexture) {
gridTexture = this.createGridTexture(dc, this._gridTextureKey);
}
gl.bindTexture(gl.TEXTURE_2D, gridTexture);
dc.frameStatistics.incrementTextureLoadCount(1);
gl.activeTexture(gl.TEXTURE1);
var simFbo = gpuResourceCache.resourceForKey(simKey);
if (!simFbo) {
simFbo = this.createSimFbo(dc, simKey);
}
gl.bindTexture(gl.TEXTURE_2D, simFbo.getSecondaryTexture());
dc.frameStatistics.incrementTextureLoadCount(1);
var quadBuffer = gpuResourceCache.resourceForKey(this._quadVboKey);
if (!quadBuffer) {
quadBuffer = this.createQuadVbo(dc);
}
var particleVbo = gpuResourceCache.resourceForKey(this._particleVboKey);
if (!particleVbo) {
particleVbo = this.createParticleVbo(dc);
}
var colorsTexure = gpuResourceCache.resourceForKey(this._colorsTextureKey);
if (!colorsTexure) {
colorsTexure = this.createColorsTexture(dc, this._colorsTextureKey);
}
var groundFbo = gpuResourceCache.resourceForKey(groundKey);
if (!groundFbo) {
groundFbo = this.createGroundFbo(dc, groundKey);
}
var sector = this.getBoundingSector(this.currentTiles, this.sector);
var bbox = this.getBoundingBox(sector, this._bbox);
var program = dc.findAndBindProgram(GridParticleProgram);
gl.enableVertexAttribArray(0);
gl.activeTexture(gl.TEXTURE2);
program.loadGridSampler(gl, gl.TEXTURE0);
program.loadSimParticleSamper(gl, gl.TEXTURE1);
program.loadTileOrColorsSampler(gl, gl.TEXTURE2);
program.loadFadeOpacity(gl, this.fadeOpacity);
program.loadSimTextureDimension(gl, this._simTextureResolution);
program.loadGridMinMax(gl, this.gridData.uMin, this.gridData.vMin, this.gridData.uMax, this.gridData.vMax);
groundFbo.bindFbo(dc);
gl.clear(glAllBuffers);
gl.viewport(0, 0, groundFbo.width, groundFbo.height);
program.loadDrawMode(gl, program.MODE_TEX_COPY_FADE);
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.bindTexture(gl.TEXTURE_2D, groundFbo.getSecondaryTexture());
dc.frameStatistics.incrementTextureLoadCount(1);
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.bindBuffer(gl.ARRAY_BUFFER, particleVbo);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
program.loadDrawMode(gl, program.MODE_DRAW_PARTICLES);
program.loadBbox(gl, bbox);
gl.bindTexture(gl.TEXTURE_2D, colorsTexure);
dc.frameStatistics.incrementTextureLoadCount(1);
gl.drawArrays(gl.POINTS, 0, this._numParticles);
groundFbo.swap();
groundFbo.isCleared = false;
/* Update particle sim */
simFbo.bindFbo(dc);
gl.clear(glAllBuffers);
gl.viewport(0, 0, this._simTextureResolution, this._simTextureResolution);
gl.disable(gl.BLEND);
program = dc.findAndBindProgram(GridParticleSimProgram);
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
program.loadGridDimensions(gl, this.gridData.width, this.gridData.height);
program.loadGridMinMax(gl, this.gridData.uMin, this.gridData.vMin, this.gridData.uMax, this.gridData.vMax);
program.loadRandSeed(gl, Math.random());
program.loadSpeedFactor(gl, this.speedFactor);
program.loadDropRate(gl, this.dropRate);
program.loadDropRateMultiplier(gl, this.dropRateMultiplier);
program.loadGridSampler(gl, gl.TEXTURE0);
program.loadParticleSamper(gl, gl.TEXTURE1);
program.loadBbox(gl, bbox);
gl.drawArrays(gl.TRIANGLES, 0, 6);
simFbo.swap();
simFbo.isCleared = false;
};
/**
* Restores the WebGL state after rendering to an offsreeen framebuffer.
* @param {DrawContext} dc
*/
GriddedDataLayer.prototype.restoreGLState = function (dc) {
var gl = dc.currentGlContext;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, dc.viewport.width, dc.viewport.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.activeTexture(gl.TEXTURE0);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
};
/**
* Clears the ground fbo's textures.
* @param {DrawContext} dc
*/
GriddedDataLayer.prototype.clearGroundFbo = function (dc) {
this._requiresFboClear = false;
var groundKey = this._groundTextureKey;
if (dc.globe.offset === 1) {
groundKey = this._groundTextureKey1;
}
else if (dc.globe.offset === -1) {
groundKey = this._groundTextureKey2;
}
var fbo = dc.gpuResourceCache.resourceForKey(groundKey);
if (fbo && !fbo.isCleared) {
fbo.clearFbo(dc);
var gl = dc.currentGlContext;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, dc.viewport.width, dc.viewport.height);
this._lastGroundFboClear = Date.now();
}
};
/**
* Binds the teture of the offscreen framebuffer to be drawn on the terrain.
* @param {DrawContext} dc
*/
GriddedDataLayer.prototype.bind = function (dc) {
var groundKey = this._groundTextureKey;
if (dc.globe.offset === 1) {
groundKey = this._groundTextureKey1;
}
else if (dc.globe.offset === -1) {
groundKey = this._groundTextureKey2;
}
var fbo = dc.gpuResourceCache.resourceForKey(groundKey);
if (!fbo) {
return false;
}
return fbo.bind(dc);
};
// Intentionally not documented.
GriddedDataLayer.prototype.applyInternalTransform = function (dc, matrix) {
};
/**
* Loads the grid json data from the specified url.
* @param {String} url
*/
GriddedDataLayer.prototype.loadGridData = function (url) {
if (this._loadInProgress) {
return;
}
if (!url) {
return;
}
var self = this;
var xhr = new XMLHttpRequest();
xhr.onload = function () {
self._loadInProgress = false;
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) {
var gribData = JSON.parse(xhr.response);
self.gridData = self.createGridData(gribData);
}
else {
throw new Error(xhr.statusText);
}
};
xhr.onerror = function () {
self._loadInProgress = false;
throw new Error('Unable to fetch data: ' + url);
};
xhr.open('GET', url, true);
xhr.send();
this._loadInProgress = true;
};
/**
* Transforms the gribData as outputed by the grib2json library to a format suitable for rendering in an offsreen framebuffer
* @param {Object} gribData The grib data as outputed by the grib2json library
* @return {Object} The grid data needed for rendering in an offsreen framebuffer
*/
GriddedDataLayer.prototype.createGridData = function (gribData) {
var u = gribData[0];
var v = gribData[1];
var width = Math.floor(u.header.nx);
var height = Math.floor(u.header.ny);
var uMin = u.data[0];
var uMax = u.data[0];
var vMin = v.data[0];
var vMax = v.data[0];
for (var i = 1, len = u.data.length; i < len; i++) {
if (uMin > u.data[i]) {
uMin = u.data[i];
}
if (uMax < u.data[i]) {
uMax = u.data[i];
}
if (vMin > v.data[i]) {
vMin = v.data[i];
}
if (vMax < v.data[i]) {
vMax = v.data[i];
}
}
var arr = new Uint8Array(width * height * 4);
var deltaU = uMax - uMin;
var delatV = vMax - vMin;
var haflWidth = Math.floor(width / 2);
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
i = (y * width + x) * 4;
var k = y * width + (x + haflWidth) % width;
arr[i + 0] = Math.floor(255 * (u.data[k] - uMin) / deltaU);
arr[i + 1] = Math.floor(255 * (v.data[k] - vMin) / delatV);
arr[i + 2] = 0;
arr[i + 3] = 255;
}
}
var gridData = {
width: width,
height: height,
uMin: uMin,
uMax: uMax,
vMin: vMin,
vMax: vMax,
data: arr
};
return gridData;
};
/**
* Computes the bounding sector for a list of tiles and saves the result in the provides sector.
* @param {Array} tiles The list of tiles
* @param {Sector} sector The sector in which to save the result
* @return {Sector} The bounding sector
*/
GriddedDataLayer.prototype.getBoundingSector = function (tiles, sector) {
if (tiles.length) {
sector.copy(tiles[0].sector);
}
for (var i = 1, len = tiles.length; i < len; i++) {
sector.union(tiles[i].sector);
}
return sector;
};
/**
* Computes a normalized bounding box for the given sector and saves the result in the provides bbox.
* The x axis coresponds with longitude, 0 coresponds with -180 and 1 coresponds with 180.
* The y axis coresponds with latitude, 0 coresponds with 90 and 1 coresponds with -90.
* @param {Sector} sector The sector for which to compute the bounding box
* @param {Float32Array} bbox An array in which to store the result. The format is: [xMin, yMin, xMax, yMax]
* @return {Array} The provided bbox
*/
GriddedDataLayer.prototype.getBoundingBox = function (sector, bbox) {
bbox[0] = (sector.minLongitude + 180) / 360;
bbox[1] = (sector.maxLatitude - 90) / -180;
bbox[2] = (sector.maxLongitude + 180) / 360;
bbox[3] = (sector.minLatitude - 90) / -180;
return bbox;
};
/**
* Removes tiles that are behind the horizon.
*
* The eyeAltitudeReduction parameter of this layer can be used to futher cull tiles that are on the edge of the globe and barely visible.
* Setting the eyeAltitudeReduction to 0 will not cull tiles that are visible.
*
* The eyeAltitudeReductionTrehshold parameter of this layer controlls if a reduction should apply.
* When the altitude is very high, tiles that are perfectly visible might get culled.
*
* @param {DrawContext} dc
* @param {Array} tiles
* @return {Array} The tiles that are visible.
*/
GriddedDataLayer.prototype.cullTilesHorizon = function (dc, tiles) {
var newTiles = [];
var altitude = dc.eyePosition.altitude;
var altitudeMultiplier = 1 - this.eyeAltitudeReduction;
if (altitude > this.eyeAltitudeReductionTrehshold) {
altitudeMultiplier = 1;
}
var horizonDistance = WWMath.horizonDistanceForGlobeRadius(dc.globe.equatorialRadius, altitude * altitudeMultiplier);
for (var i = 0, len = tiles.length; i < len; i++) {
var tile = tiles[i];
var distance = tile.distanceTo(dc.eyePoint);
if (distance <= horizonDistance) {
newTiles.push(tile);
}
}
return newTiles;
};
// Documented in superclass.
GriddedDataLayer.prototype.retrieveTileImage = function (dc, tile, suppressRedraw) {
this.currentTilesInvalid = true;
if (!suppressRedraw) {
// Send an event to request a redraw.
var e = document.createEvent('Event');
e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
window.dispatchEvent(e);
}
};
// Documented in superclass.
GriddedDataLayer.prototype.addTile = function (dc, tile) {
tile.fallbackTile = null;
tile.opacity = 1;
this.currentTiles.push(tile);
};
/**
* Creates a DoubleBufferedFbo for the particle simulation.
*
* @param {DrawContext} dc
* @param {String} key The gpuCache key
* @return {DoubleBufferedFbo}
*/
GriddedDataLayer.prototype.createSimFbo = function (dc, key) {
this._simTextureResolution = Math.ceil(Math.sqrt(this._numParticles));
this._numParticles = this._simTextureResolution * this._simTextureResolution;
var particles = new Uint8Array(this._numParticles * 4);
for (var i = 0, len = particles.length; i < len; i++) {
particles[i] = Math.floor(Math.random() * 256);
}
var options = { width: this._simTextureResolution, height: this._simTextureResolution };
var texture1 = this.createTexture(dc, particles, options);
var texture2 = this.createTexture(dc, particles, options);
var simFbo = new DoubleBufferedFbo(dc, texture1, texture2, options.width, options.height);
dc.gpuResourceCache.putResource(key, simFbo, simFbo.size);
return simFbo;
};
/**
* Creates a DoubleBufferedFbo for the particle ground texture.
*
* @param {DrawContext} dc
* @param {String} key The gpuCache key
* @return {DoubleBufferedFbo}
*/
GriddedDataLayer.prototype.createGroundFbo = function (dc, key) {
var gl = dc.currentGlContext;
var options = { width: this._groundTextureResolution, height: this._groundTextureResolution };
options[gl.TEXTURE_MIN_FILTER] = gl.NEAREST;
options[gl.TEXTURE_MAG_FILTER] = gl.NEAREST;
var pixels = new Uint8Array(options.width * options.height * 4);
var texture1 = this.createTexture(dc, pixels, options);
var texture2 = this.createTexture(dc, pixels, options);
var groundFbo = new DoubleBufferedFbo(dc, texture1, texture2, options.width, options.height);
dc.gpuResourceCache.putResource(key, groundFbo, groundFbo.size);
return groundFbo;
};
/**
* Creates a WebGLTexture for the color gradient.
*
* @param {DrawContext} dc
* @param {String} key The gpuCache key
* @return {WebGLTexture}
*/
GriddedDataLayer.prototype.createColorsTexture = function (dc, key) {
var gl = dc.currentGlContext;
var pixles = this.createColorGradient(this._colors);
var options = { width: 16, height: 16 };
options[gl.TEXTURE_MIN_FILTER] = gl.LINEAR;
options[gl.TEXTURE_MAG_FILTER] = gl.LINEAR;
var texture = this.createTexture(dc, pixles, options);
var size = options.width * options.height * 4;
dc.gpuResourceCache.putResource(key, texture, size);
return texture;
};
/**
* Creates a WebGLTexture for the grib data.
*
* @param {DrawContext} dc
* @param {String} key The gpuCache key
* @return {WebGLTexture}
*/
GriddedDataLayer.prototype.createGridTexture = function (dc, key) {
var gl = dc.currentGlContext;
var pixles = this.gridData.data;
var options = { width: this.gridData.width, height: this.gridData.height };
options[gl.TEXTURE_MIN_FILTER] = gl.LINEAR;
options[gl.TEXTURE_MAG_FILTER] = gl.LINEAR;
var texture = this.createTexture(dc, pixles, options);
var size = options.width * options.height * 4;
dc.gpuResourceCache.putResource(key, texture, size);
return texture;
};
/**
* Creates a WebGLBuffer for the particles.
*
* @param {DrawContext} dc
* @param {String} key The gpuCache key
* @return {WebGLBuffer}
*/
GriddedDataLayer.prototype.createParticleVbo = function (dc) {
var gl = dc.currentGlContext;
var len = this._numParticles * 2;
var particles = new Float32Array(len);
var idx = 0;
for (var i = 0; i < len; i += 2) {
particles[i] = idx++;
}
var vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, particles, gl.STATIC_DRAW);
dc.gpuResourceCache.putResource(this._particleVboKey, vbo, particles.byteLength);
return vbo;
};
/**
* Creates a WebGLBuffer for a quad.
*
* @param {DrawContext} dc
* @param {String} key The gpuCache key
* @return {WebGLBuffer}
*/
GriddedDataLayer.prototype.createQuadVbo = function (dc) {
var gl = dc.currentGlContext;
var data = new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]);
var vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
dc.gpuResourceCache.putResource(this._quadVboKey, vbo, data.byteLength);
return vbo;
};
/**
* Creates a WebGLTexture for a given typed array and options.
*
* @param {DrawContext} dc
* @param {Unit8Array} data A typped array, ussually a Unit8Array
* @param {Object} options Options for the width, height, wrap, min and max filters. Width and height are mandatory.
* @return {WebGLTexture}
*/
GriddedDataLayer.prototype.createTexture = function (dc, data, options) {
var gl = dc.currentGlContext;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options[gl.TEXTURE_WRAP_S] || gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options[gl.TEXTURE_WRAP_T] || gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, options[gl.TEXTURE_MIN_FILTER] || gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, options[gl.TEXTURE_MAG_FILTER] || gl.NEAREST);
var level = options.level || 0;
var format = options.format || gl.RGBA;
var type = options.type || gl.UNSIGNED_BYTE;
var width = options.width;
var height = options.height;
var border = 0;
gl.texImage2D(gl.TEXTURE_2D, level, format, width, height, border, format, type, data);
return texture;
};
/**
* Creates a color gradient for the given colors.
*
* @param {Object} colors An object with the keys as numbers between 0 - 1 and the values as a valid css color string.
* @return {Uint8ClampedArray}
*/
GriddedDataLayer.prototype.createColorGradient = function (colors) {
var canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 1;
var ctx = canvas.getContext('2d');
var gradient = ctx.createLinearGradient(0, 0, 256, 0);
var stops = Object.keys(colors);
for (var i = 0, len = stops.length; i < len; i++) {
var stop = stops[i];
gradient.addColorStop(Number(stop), colors[stop]);
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 1);
return ctx.getImageData(0, 0, 256, 1).data;
};
return GriddedDataLayer;
});