497 lines
13 KiB
JavaScript
497 lines
13 KiB
JavaScript
/**
|
|
* @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;
|