/*
* 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 GridParticleSimProgram
*/
define([
'../error/ArgumentError',
'./GpuProgram',
'../util/Logger'
],
function (
ArgumentError,
GpuProgram,
Logger) {
'use strict';
/**
* Constructs a new program.
* Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
* <p>
* This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program. This
* method then compiles the shaders and then links the program if compilation is successful. Use the bind method to make the
* program current during rendering.
*
* @alias GridParticleSimProgram
* @constructor
* @augments GpuProgram
* @classdesc GridParticleSimProgram is a GLSL program that simulates particle movement.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @throws {ArgumentError} If the shaders cannot be compiled, or linking of
* the compiled shaders into a program fails.
*/
var GridParticleSimProgram = function (gl) {
var vertexShaderSource =
'attribute vec2 vertexPosition;\n' +
'varying vec2 texureCoord;\n' +
'void main() {\n' +
' texureCoord = vertexPosition;\n' +
' gl_Position = vec4(1.0 - 2.0 * vertexPosition, 0, 1);\n' +
'}';
var fragmentShaderSource =
'#ifdef GL_FRAGMENT_PRECISION_HIGH\n' +
'precision highp float;\n' +
'#else\n' +
'precision mediump float;\n' +
'#endif\n' +
'uniform sampler2D particleSampler;\n' +
'uniform sampler2D gridSampler;\n' +
'uniform vec2 gridDimensions;\n' +
'uniform vec4 gridMinMax;\n' +
'uniform float randSeed;\n' +
'uniform float speedFactor;\n' +
'uniform float dropRate;\n' +
'uniform float dropRateMultiplier;\n' +
'uniform vec4 bbox;\n' +
'varying vec2 texureCoord;\n' +
'const vec2 bitEnc = vec2(1.0, 255.0);\n' +
'const vec2 bitDec = 1.0 / bitEnc;\n' +
// pseudo-random generator
'const vec3 rand_constants = vec3(12.9898, 78.233, 4375.85453);\n' +
'float rand(const vec2 co) {\n' +
' float t = dot(rand_constants.xy, co);\n' +
' return fract(sin(t) * (rand_constants.z + t));\n' +
'}\n' +
// bilinear filtering based on 4 adjacent pixels for smooth interpolation
'vec2 lookupGridVector(const vec2 uv) {\n' +
' vec2 px = 1.0 / gridDimensions;\n' +
' vec2 vc = (floor(uv * gridDimensions)) * px;\n' +
' vec2 f = fract(uv * gridDimensions);\n' +
' vec2 tl = texture2D(gridSampler, vc).rg;\n' +
' vec2 tr = texture2D(gridSampler, vc + vec2(px.x, 0)).rg;\n' +
' vec2 bl = texture2D(gridSampler, vc + vec2(0, px.y)).rg;\n' +
' vec2 br = texture2D(gridSampler, vc + px).rg;\n' +
' return mix(mix(tl, tr, f.x), mix(bl, br, f.x), f.y);\n' +
'}\n' +
'vec2 decodePositionFromRGBA(const vec4 color) {\n' +
' vec4 rounded_color = floor(color * 255.0 + 0.5) / 255.0;\n' +
' float x = dot(rounded_color.rg, bitDec);\n' +
' float y = dot(rounded_color.ba, bitDec);\n' +
' return vec2(x, y);\n' +
'}\n' +
'vec4 encodePositionToRGBA(const vec2 pos) {\n' +
' vec2 rg = bitEnc * pos.x;\n' +
' rg = fract(rg);\n' +
' rg -= rg.yy * vec2(1.0 / 255.0, 0.0);\n' +
' vec2 ba = bitEnc * pos.y;\n' +
' ba = fract(ba);\n' +
' ba -= ba.yy * vec2(1.0 / 255.0, 0.0);\n' +
' return vec4(rg, ba);\n' +
'}\n' +
'void main() {\n' +
' vec4 color = texture2D(particleSampler, texureCoord);\n' +
' vec2 pos = decodePositionFromRGBA(color);\n' +
' vec2 global_pos = bbox.xy + pos * (bbox.zw - bbox.xy);\n' +
' vec2 normalizedGridVector = lookupGridVector(global_pos);\n' +
' vec2 gridVector = mix(gridMinMax.xy, gridMinMax.zw, normalizedGridVector);\n' +
' float normalizedDistance = length(gridVector) / length(gridMinMax.zw);\n' +
//particles closer to the poles should move faster
' float distortion = max(cos(radians(global_pos.y * 180.0 - 90.0)), 0.00001);\n' +
//reduce particle speed as we are not using real earth dimensions for the texture
' float speedReduction = 0.0001;\n' +
' vec2 offset = vec2(gridVector.x / distortion, -gridVector.y) * speedReduction * speedFactor;\n' +
// update particle position, wrapping around the date line
' pos = fract(1.0 + pos + offset);\n' +
// randomly restart a particle at random position, to avoid degeneration
' float dropRating = dropRate + normalizedDistance * dropRateMultiplier;\n' +
' vec2 seed = (pos + texureCoord) * randSeed;\n' +
' float retain = step(dropRating, rand(seed));\n' +
' vec2 randomPos = vec2(rand(seed + 1.3), 1.0 - rand(seed + 2.1));\n' +
' pos = mix(pos, randomPos, 1.0 - retain);\n' +
' gl_FragColor = encodePositionToRGBA(pos);\n' +
'}'
// Call to the superclass, which performs shader program compiling and linking.
GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, ['vertexPosition']);
/**
* The WebGL location for this program's 'vertexPosition' attribute.
* @type {Number}
* @readonly
*/
this.vertexPointLocation = this.attributeLocation(gl, 'vertexPosition');
/**
* The WebGL location for this program's 'particleSampler' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.particleSamplerLocation = this.uniformLocation(gl, "particleSampler");
/**
* The WebGL location for this program's 'gridSampler' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.gridSamplerLocation = this.uniformLocation(gl, 'gridSampler');
/**
* The WebGL location for this program's 'gridDimensions' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.gridDimensionsLocation = this.uniformLocation(gl, 'gridDimensions');
/**
* The WebGL location for this program's 'gridMinMax' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.gridMinMaxLocation = this.uniformLocation(gl, 'gridMinMax');
/**
* The WebGL location for this program's 'randSeed' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.randSeedLocation = this.uniformLocation(gl, 'randSeed');
/**
* The WebGL location for this program's 'speedFactor' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.speedFactorLocation = this.uniformLocation(gl, 'speedFactor');
/**
* The WebGL location for this program's 'dropRate' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.dropRateLocation = this.uniformLocation(gl, 'dropRate');
/**
* The WebGL location for this program's 'dropRateMultiplier' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.dropRateMultiplierLocation = this.uniformLocation(gl, 'dropRateMultiplier');
/**
* The WebGL location for this program's 'bbox' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.bboxLocation = this.uniformLocation(gl, 'bbox');
};
/**
* A string that uniquely identifies this program.
* @type {string}
* @readonly
*/
GridParticleSimProgram.key = 'WorldWindGpuGridParticleSimProgram';
// Inherit from GpuProgram.
GridParticleSimProgram.prototype = Object.create(GpuProgram.prototype);
/**
* Loads the specified width and height as the value of this program's 'gridDimensions' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} width The width of the grid.
* @param {Number} height The height of the grid.
* @throws {ArgumentError} If the specified width or height are missing.
*/
GridParticleSimProgram.prototype.loadGridDimensions = function (gl, width, height) {
if (width == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadGridDimensions', 'missing width'));
}
if (height == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadGridDimensions', 'missing height'));
}
gl.uniform2f(this.gridDimensionsLocation, width, height);
};
/**
* Loads the min and max u and v vectors of the grid as the value of this program's 'gridMinMaxLocation' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} uMin The minimum u value of the grid data.
* @param {Number} vMin The minimum v value of the grid data.
* @param {Number} uMax The maximum u value of the grid data.
* @param {Number} vMax The maximum v value of the grid data.
* @throws {ArgumentError} If the specified u and v values are missing.
*/
GridParticleSimProgram.prototype.loadGridMinMax = function (gl, uMin, vMin, uMax, vMax) {
if (uMin == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadGridMinMax', 'missing uMin'));
}
if (vMin == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadGridMinMax', 'missing vMin'));
}
if (uMax == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadGridMinMax', 'missing uMax'));
}
if (vMax == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadGridMinMax', 'missing vMax'));
}
gl.uniform4f(this.gridMinMaxLocation, uMin, vMin, uMax, vMax);
};
/**
* Loads a random value as the value of this program's 'randSeedLocation' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} seed A random value between [0, 1).
* @throws {ArgumentError} If the specified value is missing.
*/
GridParticleSimProgram.prototype.loadRandSeed = function (gl, seed) {
if (seed == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadRandSeed', 'missing seed'));
}
gl.uniform1f(this.randSeedLocation, seed);
};
/**
* Loads the specified speedFactor as the value of this program's 'speedFactorLocation' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} speedFactor Controls how fast the particles move.
* @throws {ArgumentError} If the specified speedFactor value is missing.
*/
GridParticleSimProgram.prototype.loadSpeedFactor = function (gl, speedFactor) {
if (speedFactor == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadSpeedFactor', 'missing speedFactor'));
}
gl.uniform1f(this.speedFactorLocation, speedFactor);
};
/**
* Loads the specified dropRate as the value of this program's 'dropRateLocation' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} dropRate Controls how often the particles move to a random place.
* @throws {ArgumentError} If the specified dropRate value is missing.
*/
GridParticleSimProgram.prototype.loadDropRate = function (gl, dropRate) {
if (dropRate == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadDropRate', 'missing dropRate'));
}
gl.uniform1f(this.dropRateLocation, dropRate);
};
/**
* Loads the specified dropRateMultiplier as the value of this program's 'dropRateMultiplierLocation' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} dropRateMultiplier Controls the drop rate of a particle relative to individual particle speed.
* @throws {ArgumentError} If the specified dropRateMultiplier value is missing.
*/
GridParticleSimProgram.prototype.loadDropRateMultiplier = function (gl, dropRateMultiplier) {
if (dropRateMultiplier == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadDropRateMultiplier', 'missing dropRateMultiplier'));
}
gl.uniform1f(this.dropRateMultiplierLocation, dropRateMultiplier);
};
/**
* Loads the specified bbox as the value of this program's 'bboxLocation' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Float32Array} bbox A normalised bounding box. (0, 0) is in the top left corner and (1, 1) is in the bottom right corner.
* @throws {ArgumentError} If the specified bbox is missing.
*/
GridParticleSimProgram.prototype.loadBbox = function (gl, bbox) {
if (bbox == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, 'GridParticleSimProgram', 'loadBbox', 'missing bbox'));
}
gl.uniform4fv(this.bboxLocation, bbox);
};
/**
* Loads the specified number as the value of this program's 'gridSamplerLocation' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} unit The texture unit.
*/
GridParticleSimProgram.prototype.loadGridSampler = function (gl, unit) {
gl.uniform1i(this.gridSamplerLocation, unit - gl.TEXTURE0);
};
/**
* Loads the specified number as the value of this program's 'particleSamplerLocation' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} unit The texture unit.
*/
GridParticleSimProgram.prototype.loadParticleSamper = function (gl, unit) {
gl.uniform1i(this.particleSamplerLocation, unit - gl.TEXTURE0);
};
return GridParticleSimProgram;
});