UNPKG

ol

Version:

OpenLayers mapping library

497 lines (417 loc) • 13 kB
/** * @module ol/renderer/webgl/FlowLayer */ import WebGLArrayBuffer from '../../webgl/Buffer.js'; import {DefaultUniform} from '../../webgl/Helper.js'; import {ARRAY_BUFFER, STATIC_DRAW} from '../../webgl.js'; import WebGLTileLayerRenderer from './TileLayer.js'; /** * @typedef {import("../../layer/Flow.js").default} LayerType */ /** * @typedef {Object} Options * @property {number} maxSpeed The maximum particle speed in the input data. * @property {number} [speedFactor=0.001] A larger factor increases the rate at which particles cross the screen. * @property {number} [particles=65536] The number of particles to render. * @property {number} [cacheSize=512] The texture cache size. * @property {string} tileVertexShader The flow tile vertex shader. * @property {string} tileFragmentShader The flow tile fragment shader. * @property {string} textureVertexShader Generic texture fragment shader. * @property {string} textureFragmentShader Generic texture fragment shader. * @property {string} particlePositionVertexShader The particle position vertex shader. * @property {string} particlePositionFragmentShader The particle position fragment shader. * @property {string} particleColorVertexShader The particle color vertex shader. * @property {string} particleColorFragmentShader The particle color fragment shader. */ /** * Shader uniforms. * @enum {string} */ export const U = { TEXTURE: 'u_texture', VELOCITY_TEXTURE: 'u_velocityTexture', POSITION_TEXTURE: 'u_positionTexture', PARTICLE_COUNT_SQRT: 'u_particleCountSqrt', MAX_SPEED: 'u_maxSpeed', GAIN: 'u_gain', OFFSET: 'u_offset', IS_FLOAT: 'u_isFloat', RANDOM_SEED: 'u_randomSeed', SPEED_FACTOR: 'u_speedFactor', DROP_RATE: 'u_dropRate', DROP_RATE_BUMP: 'u_dropRateBump', OPACITY: 'u_opacity', ROTATION: DefaultUniform.ROTATION, VIEWPORT_SIZE_PX: DefaultUniform.VIEWPORT_SIZE_PX, }; /** * Shader attributes. * @enum {string} */ export const A = { POSITION: 'a_position', INDEX: 'a_index', }; /** * Shader varyings. * @enum {string} */ export const V = { POSITION: 'v_position', }; /** * @classdesc * Experimental WebGL renderer for vector fields. * @extends {WebGLTileLayerRenderer<LayerType>} */ class FlowLayerRenderer extends WebGLTileLayerRenderer { /** * @param {LayerType} layer The tiled field layer. * @param {Options} options The renderer options. */ constructor(layer, options) { super(layer, { vertexShader: options.tileVertexShader, fragmentShader: options.tileFragmentShader, cacheSize: options.cacheSize, // TODO: rework the post-processing logic // see https://github.com/openlayers/openlayers/issues/16120 postProcesses: [{}], uniforms: { [U.MAX_SPEED]: options.maxSpeed, }, }); /** * @type {string} * @private */ this.particleColorFragmentShader_ = options.particleColorFragmentShader; /** * @type {WebGLTexture|null} * @private */ this.velocityTexture_ = null; /** * @type {number} * @private */ this.particleCountSqrt_ = options.particles ? Math.ceil(Math.sqrt(options.particles)) : 256; /** * @type {WebGLArrayBuffer} * @private */ this.particleIndexBuffer_; /** * @type {WebGLArrayBuffer} * @private */ this.quadBuffer_; /** * @type {WebGLProgram} * @private */ this.particlePositionProgram_; /** * @type {string} * @private */ this.particlePositionVertexShader_ = options.particlePositionVertexShader; /** * @type {string} * @private */ this.particlePositionFragmentShader_ = options.particlePositionFragmentShader; /** * @type {WebGLTexture} * @private */ this.previousPositionTexture_; /** * @type {WebGLTexture} * @private */ this.nextPositionTexture_; /** * @type {WebGLProgram} * @private */ this.particleColorProgram_; /** * @type {string} * @private */ this.particleColorVertexShader_ = options.particleColorVertexShader; /** * @type {string} * @private */ this.particleColorFragmentShader_ = options.particleColorFragmentShader; /** * @type {WebGLProgram} * @private */ this.textureProgram_; /** * @type {string} * @private */ this.textureVertexShader_ = options.textureVertexShader; /** * @type {string} * @private */ this.textureFragmentShader_ = options.textureFragmentShader; /** * @type {WebGLTexture} * @private */ this.previousTrailsTexture_; /** * @type {WebGLTexture} * @private */ this.nextTrailsTexture_; /** * @type {number} * @private */ this.fadeOpacity_ = 0.996; // how fast the particle trails fade on each frame /** * @type {number} * @private */ this.maxSpeed_ = options.maxSpeed; /** * @type {number} * @private */ this.speedFactor_ = options.speedFactor || 0.001; /** * @type {number} * @private */ this.dropRate_ = 0.003; // how often the particles move to a random place /** * @type {number} * @private */ this.dropRateBump_ = 0.01; // drop rate increase relative to individual particle speed /** * @type {Array<number>} * @private */ this.tempVec2_ = [0, 0]; /** * @type {number} * @private */ this.renderedWidth_ = 0; /** * @type {number} * @private */ this.renderedHeight_ = 0; } /** * @override */ afterHelperCreated() { super.afterHelperCreated(); const helper = this.helper; const gl = helper.getGL(); this.framebuffer_ = gl.createFramebuffer(); const particleCount = this.particleCountSqrt_ * this.particleCountSqrt_; const particleIndices = new Float32Array(particleCount); for (let i = 0; i < particleCount; ++i) { particleIndices[i] = i; } const particleIndexBuffer = new WebGLArrayBuffer(ARRAY_BUFFER, STATIC_DRAW); particleIndexBuffer.setArray(particleIndices); helper.flushBufferData(particleIndexBuffer); this.particleIndexBuffer_ = particleIndexBuffer; const quadIndices = new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]); const quadBuffer = new WebGLArrayBuffer(ARRAY_BUFFER, STATIC_DRAW); quadBuffer.setArray(quadIndices); helper.flushBufferData(quadBuffer); this.quadBuffer_ = quadBuffer; const particlePositions = new Uint8Array(particleCount * 4); for (let i = 0; i < particlePositions.length; ++i) { particlePositions[i] = Math.floor(Math.random() * 256); } this.previousPositionTexture_ = helper.createTexture( [this.particleCountSqrt_, this.particleCountSqrt_], particlePositions, null, true, ); this.nextPositionTexture_ = helper.createTexture( [this.particleCountSqrt_, this.particleCountSqrt_], particlePositions, null, true, ); this.particlePositionProgram_ = helper.getProgram( this.particlePositionFragmentShader_, this.particlePositionVertexShader_, ); this.particleColorProgram_ = helper.getProgram( this.particleColorFragmentShader_, this.particleColorVertexShader_, ); this.textureProgram_ = helper.getProgram( this.textureFragmentShader_, this.textureVertexShader_, ); } createSizeDependentTextures_() { const helper = this.helper; const gl = helper.getGL(); const canvas = helper.getCanvas(); const screenWidth = canvas.width; const screenHeight = canvas.height; const blank = new Uint8Array(screenWidth * screenHeight * 4); if (this.nextTrailsTexture_) { gl.deleteTexture(this.nextTrailsTexture_); } this.nextTrailsTexture_ = helper.createTexture( [screenWidth, screenHeight], blank, null, true, ); if (this.previousTrailsTexture_) { gl.deleteTexture(this.previousTrailsTexture_); } this.previousTrailsTexture_ = helper.createTexture( [screenWidth, screenHeight], blank, null, true, ); } /** * @override * @param {import("../../Map.js").FrameState} frameState Frame state. */ beforeFinalize(frameState) { const helper = this.helper; const gl = helper.getGL(); const canvas = helper.getCanvas(); const screenWidth = canvas.width; const screenHeight = canvas.height; if ( this.renderedWidth_ != screenWidth || this.renderedHeight_ != screenHeight ) { this.createSizeDependentTextures_(); } const size = [screenWidth, screenHeight]; // copy current frame buffer to the velocity texture this.velocityTexture_ = helper.createTexture( size, null, this.velocityTexture_, ); gl.copyTexImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, screenWidth, screenHeight, 0, ); this.drawParticleTrails_(frameState); this.updateParticlePositions_(frameState); frameState.animate = true; this.renderedWidth_ = screenWidth; this.renderedHeight_ = screenHeight; } /** * @param {import("../../Map.js").FrameState} frameState Frame state. */ drawParticleTrails_(frameState) { const helper = this.helper; const gl = helper.getGL(); helper.bindFrameBuffer(this.framebuffer_, this.nextTrailsTexture_); this.drawTexture_(this.previousTrailsTexture_, this.fadeOpacity_); this.drawParticleColor_(frameState); helper.bindInitialFrameBuffer(); gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.clear(gl.COLOR_BUFFER_BIT); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); this.drawTexture_(this.nextTrailsTexture_, 1); gl.disable(gl.BLEND); const current = this.nextTrailsTexture_; this.nextTrailsTexture_ = this.previousTrailsTexture_; this.previousTrailsTexture_ = current; } /** * @param {WebGLTexture} texture The texture to draw. * @param {number} opacity The opacity. */ drawTexture_(texture, opacity) { const helper = this.helper; const gl = helper.getGL(); helper.useProgram(this.textureProgram_); helper.bindTexture(texture, 0, U.TEXTURE); helper.bindAttribute(this.quadBuffer_, A.POSITION, 2); this.helper.setUniformFloatValue(U.OPACITY, opacity); gl.drawArrays(gl.TRIANGLES, 0, 6); } /** * @param {import("../../Map.js").FrameState} frameState Frame state. */ drawParticleColor_(frameState) { const helper = this.helper; const gl = helper.getGL(); helper.useProgram(this.particleColorProgram_); const particleCount = this.particleCountSqrt_ * this.particleCountSqrt_; helper.bindAttribute(this.particleIndexBuffer_, A.INDEX, 1); helper.bindTexture(this.previousPositionTexture_, 0, U.POSITION_TEXTURE); helper.bindTexture(this.velocityTexture_, 1, U.VELOCITY_TEXTURE); this.helper.setUniformFloatValue( U.PARTICLE_COUNT_SQRT, this.particleCountSqrt_, ); const rotation = this.tempVec2_; rotation[0] = Math.cos(-frameState.viewState.rotation); rotation[1] = Math.sin(-frameState.viewState.rotation); this.helper.setUniformFloatVec2(U.ROTATION, rotation); this.helper.setUniformFloatValue(U.MAX_SPEED, this.maxSpeed_); gl.drawArrays(gl.POINTS, 0, particleCount); } /** * @param {import("../../Map.js").FrameState} frameState Frame state. */ updateParticlePositions_(frameState) { const helper = this.helper; const gl = helper.getGL(); helper.useProgram(this.particlePositionProgram_); gl.viewport(0, 0, this.particleCountSqrt_, this.particleCountSqrt_); helper.bindFrameBuffer(this.framebuffer_, this.nextPositionTexture_); helper.bindTexture(this.previousPositionTexture_, 0, U.POSITION_TEXTURE); helper.bindTexture(this.velocityTexture_, 1, U.VELOCITY_TEXTURE); helper.bindAttribute(this.quadBuffer_, A.POSITION, 2); helper.setUniformFloatValue(U.RANDOM_SEED, Math.random()); helper.setUniformFloatValue(U.SPEED_FACTOR, this.speedFactor_); helper.setUniformFloatValue(U.DROP_RATE, this.dropRate_); helper.setUniformFloatValue(U.DROP_RATE_BUMP, this.dropRateBump_); const rotation = this.tempVec2_; rotation[0] = Math.cos(-frameState.viewState.rotation); rotation[1] = Math.sin(-frameState.viewState.rotation); this.helper.setUniformFloatVec2(U.ROTATION, rotation); const size = frameState.size; this.helper.setUniformFloatVec2(U.VIEWPORT_SIZE_PX, [size[0], size[1]]); gl.drawArrays(gl.TRIANGLES, 0, 6); const current = this.nextPositionTexture_; this.nextPositionTexture_ = this.previousPositionTexture_; this.previousPositionTexture_ = current; } } export default FlowLayerRenderer;