Source: skunami.js

/**
 * @fileOverview GPU height field water simulations for Three.js flat planes
 * @author Skeel Lee <skeel@skeelogy.com>
 * @version 1.0.2
 *
 * @example
 * //How to setup a water sim:
 *
 * //create a plane as the water
 * var WATER_SIZE = 10;
 * var WATER_RES = 256;
 * waterGeom = new THREE.PlaneGeometry(WATER_SIZE, WATER_SIZE, WATER_RES - 1, WATER_RES - 1);
 * waterGeom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2));
 * waterMesh = new THREE.Mesh(waterGeom, null);  //a custom material will automatically be assigned later
 * scene.add(waterMesh);
 *
 * //create a GpuPipeModelWater instance (as an example)
 * var gpuWater = new SKUNAMI.GpuPipeModelWater({
 *     renderer: renderer,
 *     scene: scene,
 *     mesh: waterMesh,
 *     size: WATER_SIZE,
 *     res: WATER_RES,
 *     dampingFactor: 0.995,
 *     multisteps: 1
 * });
 *
 * //update every frame
 * renderer.clear();
 * gpuWater.update(dt);  //have to do this after clear but before render
 * renderer.render(scene, camera);
 *
 * @example
 * //How to interact with the water:
 *
 * //disturb (i.e. cause ripples on water surface)
 * var position = detectIntersection();  //do ray-intersection tests, for example, to determine where the user is clicking on the water plane
 * var waterDisturbAmount = 0.15;
 * var waterDisturbRadius = 0.25;
 * gpuWater.disturb(position, waterDisturbAmount, waterDisturbRadius);
 *
 * //source (i.e. add water to simulation, only available for GpuPipeModelWater)
 * var waterSourceAmount = 0.2;
 * var waterSourceRadius = 0.7;
 * gpuWater.source(position, waterSourceAmount, waterSourceRadius);
 *
 * //sink (i.e. remove water from simulation, only available for GpuPipeModelWater)
 * var waterSinkAmount = -0.5;
 * var waterSinkRadius = 0.7;
 * gpuWater.source(position, waterSinkAmount, waterSinkRadius);
 *
 * @example
 * //How to flood the scene over time:
 *
 * var floodRate = 10;  //cubic scene units per unit time
 *
 * //add some volume every frame
 * var dV = floodRate * dt;
 * gpuWater.flood(dV);
 */

/**
 * @namespace
 */
var SKUNAMI = SKUNAMI || { version: '1.0.2' };
console.log('Using SKUNAMI ' + SKUNAMI.version);

/**
 * Abstract base class for GPU height field water simulations
 * @constructor
 * @abstract
 */
SKUNAMI.GpuHeightFieldWater = function (options) {

    if (typeof options.mesh === 'undefined') {
        throw new Error('mesh not specified');
    }
    this.__mesh = options.mesh;
    if (typeof options.renderer === 'undefined') {
        throw new Error('renderer not specified');
    }
    this.__renderer = options.renderer;
    if (typeof options.size === 'undefined') {
        throw new Error('size not specified');
    }
    this.__size = options.size;
    if (typeof options.scene === 'undefined') {
        throw new Error('scene not specified');
    }
    this.__scene = options.scene;
    if (typeof options.res === 'undefined') {
        throw new Error('res not specified');
    }
    this.__res = options.res;
    if (typeof options.dampingFactor === 'undefined') {
        throw new Error('dampingFactor not specified');
    }
    this.__dampingFactor = options.dampingFactor;

    //number of full steps to take per frame, to speed up some of algorithms that are slow to propagate at high mesh resolutions.
    //this is different from substeps which are reduces dt per step for stability.
    this.__multisteps = options.multisteps || 1;

    this.__shouldDisplayWaterTexture = false;
    this.__shouldDisplayObstaclesTexture = false;

    this.__gravity = 9.81;
    this.__density = options.density || 1000;  //default to 1000 kg per cubic metres

    this.__halfSize = this.__size / 2.0;
    this.__segmentSize = this.__size / this.__res;
    this.__segmentSizeSquared = this.__segmentSize * this.__segmentSize;
    this.__texelSize = 1.0 / this.__res;

    this.__disturbMapHasUpdated = false;
    this.__isDisturbing = false;
    this.__disturbUvPos = new THREE.Vector2();
    this.__disturbAmount = 0;
    this.__disturbRadius = 0.0025 * this.__size;

    this.__linearFloatRgbaParams = {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        wrapS: THREE.ClampToEdgeWrapping,
        wrapT: THREE.ClampToEdgeWrapping,
        format: THREE.RGBAFormat,
        stencilBuffer: false,
        depthBuffer: false,
        type: THREE.FloatType
    };

    this.__nearestFloatRgbaParams = {
        minFilter: THREE.NearestFilter,
        magFilter: THREE.NearestFilter,
        wrapS: THREE.ClampToEdgeWrapping,
        wrapT: THREE.ClampToEdgeWrapping,
        format: THREE.RGBAFormat,
        stencilBuffer: false,
        depthBuffer: false,
        type: THREE.FloatType
    };

    //create a boundary texture
    this.__boundaryData = new Float32Array(4 * this.__res * this.__res);

    //camera depth range (for obstacles)
    this.__rttObstaclesCameraRange = 50.0;

    this.__pixelByteData = new Uint8Array(this.__res * this.__res * 4);

    this.__staticObstacles = [];
    this.__dynObstacles = [];
    this.__shouldUpdateStaticObstacle = false;

    this.__callbacks = {};

    this.__initCounter = 5;
    this.__init();

    //setup obstacles
    this.__setupObstaclesScene();
};
/**
 * Gets whether the water texture should be displayed
 * @returns {boolean} Whether the water texture should be displayed
 */
SKUNAMI.GpuHeightFieldWater.prototype.getShouldDisplayWaterTexture = function () {
    return this.__shouldDisplayWaterTexture;
};
/**
 * Sets whether the water texture should be displayed
 * @param {boolean} value Whether the water texture should be displayed
 */
SKUNAMI.GpuHeightFieldWater.prototype.setShouldDisplayWaterTexture = function (value) {
    this.__shouldDisplayWaterTexture = value;
};
/**
 * Gets whether the obstacles texture should be displayed
 * @returns {boolean} Whether the obstacles texture should be displayed
 */
SKUNAMI.GpuHeightFieldWater.prototype.getShouldDisplayObstaclesTexture = function () {
    return this.__shouldDisplayObstaclesTexture;
};
/**
 * Sets whether the obstacles texture should be displayed
 * @param {boolean} value Whether the obstacles texture should be displayed
 */
SKUNAMI.GpuHeightFieldWater.prototype.setShouldDisplayObstaclesTexture = function (value) {
    this.__shouldDisplayObstaclesTexture = value;
};
SKUNAMI.GpuHeightFieldWater.prototype.__init = function () {

    this.__checkExtensions();
    this.__setupRttScene();

    //setup a reset material for clearing render targets
    this.__resetMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uColor: { type: 'v4', value: new THREE.Vector4() }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['setColor']
    });

    //create an empty texture because the default value of textures does not seem to be 0?
    if (this.__supportsTextureFloatLinear) {
        this.__emptyTexture = new THREE.WebGLRenderTarget(this.__res, this.__res, this.__linearFloatRgbaParams);
    } else {
        this.__emptyTexture = new THREE.WebGLRenderTarget(this.__res, this.__res, this.__nearestFloatRgbaParams);
    }
    this.__emptyTexture.generateMipmaps = false;
    this.__clearRenderTarget(this.__emptyTexture, 0.0, 0.0, 0.0, 0.0);

    //create a DataTexture for the boundary, with filtering type based on whether linear filtering is available
    if (this.__supportsTextureFloatLinear) {
        //use linear with mipmapping
        this.__boundaryTexture = new THREE.DataTexture(null, this.__res, this.__res, THREE.RGBAFormat, THREE.FloatType);
        this.__boundaryTexture.generateMipmaps = true;
    } else {
        //resort to nearest filter only, without mipmapping
        this.__boundaryTexture = new THREE.DataTexture(null, this.__res, this.__res, THREE.RGBAFormat, THREE.FloatType, undefined, THREE.ClampToEdgeWrapping, THREE.ClampToEdgeWrapping, THREE.NearestFilter, THREE.NearestFilter);
        this.__boundaryTexture.generateMipmaps = false;
    }
    this.__initDataAndTextures();

    this.__setupRttRenderTargets();
    this.__setupShaders();
    this.__setupVtf();

    //init parallel reducer
    this.__pr = new SKPR.ParallelReducer(this.__renderer, this.__res, 1);
};
SKUNAMI.GpuHeightFieldWater.prototype.__getWaterFragmentShaderContent = function () {
    throw new Error('Abstract method not implemented');
};
SKUNAMI.GpuHeightFieldWater.prototype.__shaders = {

    vert: {

        pass: [

            //Pass-through vertex shader for passing just the transformed position to fragment shader

            "void main() {",
                "gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
            "}"

        ].join('\n'),

        passUv: [

            //Pass-through vertex shader for passing interpolated UVs to fragment shader

            "varying vec2 vUv;",

            "void main() {",
                "vUv = vec2(uv.x, uv.y);",
                "gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
            "}"

        ].join('\n'),

        heightMap: [

            //Vertex shader that displaces vertices in local Y based on a texture

            "uniform sampler2D uTexture;",
            "uniform vec2 uTexelSize;",
            "uniform vec2 uTexelWorldSize;",
            "uniform float uHeightMultiplier;",

            "varying vec3 vViewPos;",
            "varying vec3 vViewNormal;",
            "varying vec2 vUv;",

            THREE.ShaderChunk['shadowmap_pars_vertex'],

            "void main() {",

                "vUv = uv;",

                //displace y based on texel value
                "vec4 t = texture2D(uTexture, vUv) * uHeightMultiplier;",
                "vec3 displacedPos = vec3(position.x, t.r, position.z);",

                //find normal
                "vec2 du = vec2(uTexelSize.r, 0.0);",
                "vec2 dv = vec2(0.0, uTexelSize.g);",
                "vec3 vecPosU = vec3(displacedPos.x + uTexelWorldSize.r,",
                                    "texture2D(uTexture, vUv + du).r * uHeightMultiplier,",
                                    "displacedPos.z) - displacedPos;",
                "vec3 vecNegU = vec3(displacedPos.x - uTexelWorldSize.r,",
                                    "texture2D(uTexture, vUv - du).r * uHeightMultiplier,",
                                    "displacedPos.z) - displacedPos;",
                "vec3 vecPosV = vec3(displacedPos.x,",
                                    "texture2D(uTexture, vUv + dv).r * uHeightMultiplier,",
                                    "displacedPos.z - uTexelWorldSize.g) - displacedPos;",
                "vec3 vecNegV = vec3(displacedPos.x,",
                                    "texture2D(uTexture, vUv - dv).r * uHeightMultiplier,",
                                    "displacedPos.z + uTexelWorldSize.g) - displacedPos;",
                "vViewNormal = normalize(normalMatrix * 0.25 * (cross(vecPosU, vecPosV) + cross(vecPosV, vecNegU) + cross(vecNegU, vecNegV) + cross(vecNegV, vecPosU)));",

                "vec4 worldPosition = modelMatrix * vec4(displacedPos, 1.0);",
                "vec4 viewPos = modelViewMatrix * vec4(displacedPos, 1.0);",
                "vViewPos = viewPos.rgb;",

                "gl_Position = projectionMatrix * viewPos;",

                THREE.ShaderChunk['shadowmap_vertex'],

            "}"

        ].join('\n')

    },

    frag: {

        lambert: [

            "uniform vec3 uBaseColor;",
            "uniform vec3 uAmbientLightColor;",
            "uniform float uAmbientLightIntensity;",

            "varying vec3 vViewPos;",
            "varying vec3 vViewNormal;",
            "varying vec2 vUv;",

            "#if MAX_DIR_LIGHTS > 0",
                "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
                "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
            "#endif",

            THREE.ShaderChunk['shadowmap_pars_fragment'],

            "void main() {",

                //ambient component
                "vec3 ambient = uAmbientLightColor * uAmbientLightIntensity;",

                //diffuse component
                "vec3 diffuse = vec3(0.0);",

                "#if MAX_DIR_LIGHTS > 0",

                    "for (int i = 0; i < MAX_DIR_LIGHTS; i++) {",
                        "vec4 lightVector = viewMatrix * vec4(directionalLightDirection[i], 0.0);",
                        "float normalModulator = dot(normalize(vViewNormal), normalize(lightVector.xyz));",
                        "diffuse += normalModulator * directionalLightColor[i];",
                    "}",

                "#endif",

                "gl_FragColor = vec4(uBaseColor * (ambient + diffuse), 1.0);",

                THREE.ShaderChunk['shadowmap_fragment'],

            "}"

        ].join('\n'),

        hfWater_disturb: [

            //Fragment shader for disturbing water simulations

            "uniform sampler2D uTexture;",
            "uniform sampler2D uStaticObstaclesTexture;",
            "uniform sampler2D uDisturbTexture;",
            "uniform int uUseObstacleTexture;",

            //disturb is masked by obstacles
            "uniform int uIsDisturbing;",
            "uniform float uDisturbAmount;",
            "uniform float uDisturbRadius;",
            "uniform vec2 uDisturbPos;",

            //source is not masked by obstacles
            "uniform int uIsSourcing;",
            "uniform float uSourceAmount;",
            "uniform float uSourceRadius;",
            "uniform vec2 uSourcePos;",

            //flood is source for every cell
            "uniform int uIsFlooding;",
            "uniform float uFloodAmount;",

            "varying vec2 vUv;",

            "void main() {",

                //read texture from previous step
                //r channel: height
                "vec4 t = texture2D(uTexture, vUv);",

                "float inObstacle;",
                "if (uUseObstacleTexture == 1) {",
                    "vec4 tObstacles = texture2D(uStaticObstaclesTexture, vUv);",
                    "inObstacle = tObstacles.r;",
                "} else {",
                    //if not using obstacle texture, it means we can just determine this info from the water height.
                    //no water means it is in obstacle.
                    "inObstacle = float(t.r < 0.0);",
                "}",

                //add disturb (will be masked by obstacles)
                "if (uIsDisturbing == 1) {",
                    "float len = length(vUv - vec2(uDisturbPos.x, 1.0 - uDisturbPos.y));",
                    "t.r += uDisturbAmount * (1.0 - smoothstep(0.0, uDisturbRadius, len)) * (1.0 - inObstacle);",
                "}",

                //add source (will not be masked by obstacles, otherwise if an area has no water, you can never source into it anymore)
                "if (uIsSourcing == 1) {",
                    "float len = length(vUv - vec2(uSourcePos.x, 1.0 - uSourcePos.y));",
                    "t.r += uSourceAmount * (1.0 - smoothstep(0.0, uSourceRadius, len));",
                "}",

                //read disturb texture and just add this amount into the system
                //r channel: disturb amount
                "vec4 tDisturb = texture2D(uDisturbTexture, vUv);",
                "t.r += tDisturb.r;",

                //add flood
                "if (uIsFlooding == 1) {",  //this is used for pipe model water only
                    "t.r += uFloodAmount;",
                "}",

                //write out to texture for next step
                "gl_FragColor = t;",
            "}"

        ].join('\n'),

        hfWater_muellerGdc2008: [

            //GPU version of "Fast Water Simulation for Games Using Height Fields" (Matthias Mueller-Fisher, GDC2008)

            //NOTE: I have added in mean height in the calculations, purely because of the flooding system.
            //It is not necessary if you do not need to rise the water level.

            "uniform sampler2D uTexture;",
            "uniform vec2 uTexelSize;",
            "uniform vec2 uTexelWorldSize;",
            "uniform float uDampingFactor;",
            "uniform float uHorizontalSpeed;",
            "uniform float uDt;",
            "uniform float uMeanHeight;",

            "varying vec2 vUv;",

            "void main() {",

                //r channel: height
                //g channel: vertical vel
                //b channel: UNUSED
                //a channel: prev mean height

                //read texture from previous step
                "vec4 t = texture2D(uTexture, vUv);",

                //remove previous mean height first to bring back to 0 height
                "t.r -= t.a;",

                //propagate
                "vec2 du = vec2(uTexelSize.r, 0.0);",
                "vec2 dv = vec2(0.0, uTexelSize.g);",
                "float acc = uHorizontalSpeed * uHorizontalSpeed * (",
                               "texture2D(uTexture,vUv+du).r",
                               "+ texture2D(uTexture,vUv-du).r",
                               "+ texture2D(uTexture,vUv+dv).r",
                               "+ texture2D(uTexture,vUv-dv).r",
                               "- 4.0 * t.a - 4.0 * t.r) / (uTexelWorldSize.x * uTexelWorldSize.x);",
                "t.g += acc * uDt;",  //TODO: use a better integrator
                "t.g *= uDampingFactor;",

                //update
                "t.r += t.g * uDt;",  //TODO: use a better integrator

                //add new mean height
                "t.r += uMeanHeight;",

                //store new mean height
                "t.a = uMeanHeight;",

                //write out to texture for next step
                "gl_FragColor = t;",
            "}"

        ].join('\n'),

        hfWater_muellerGdc2008Hw: [

            //GPU version of HelloWorld code of "Fast Water Simulation for Games Using Height Fields" (Matthias Mueller-Fisher, GDC2008)

            //NOTE: I have added in mean height in the calculations, purely because of the flooding system.
            //It is not necessary if you do not need to rise the water level.

            "uniform sampler2D uTexture;",
            "uniform vec2 uTexelSize;",
            "uniform float uDampingFactor;",
            "uniform float uMeanHeight;",

            "varying vec2 vUv;",

            "void main() {",

                //r channel: height
                //g channel: vertDeriv
                //b channel: UNUSED
                //a channel: prev mean height

                //read texture from previous step
                "vec4 t = texture2D(uTexture, vUv);",

                //remove previous mean height first to bring back to 0 height
                "t.r -= t.a;",

                //propagate
                "vec2 du = vec2(uTexelSize.r, 0.0);",
                "vec2 dv = vec2(0.0, uTexelSize.g);",
                "t.g += 0.25 * (texture2D(uTexture,vUv+du).r",
                               "+ texture2D(uTexture,vUv-du).r",
                               "+ texture2D(uTexture,vUv+dv).r",
                               "+ texture2D(uTexture,vUv-dv).r - 4.0 * t.a) - t.r;",
                "t.g *= uDampingFactor;",

                //update
                "t.r += t.g;",

                //add new mean height
                "t.r += uMeanHeight;",

                //store new mean height
                "t.a = uMeanHeight;",

                //write out to texture for next step
                "gl_FragColor = t;",
            "}"

        ].join('\n'),

        hfWater_xWater: [

            //GPU version of X Water

            //NOTE: I have added in mean height in the calculations, purely because of the flooding system.
            //It is not necessary if you do not need to rise the water level.

            "uniform sampler2D uTexture;",
            "uniform vec2 uTexelSize;",
            "uniform float uDampingFactor;",
            "uniform float uMeanHeight;",

            "varying vec2 vUv;",

            "void main() {",

                //r channel: height
                //g channel: field1
                //b channel: field2
                //a channel: prev mean height

                //read texture from previous step
                "vec4 t = texture2D(uTexture, vUv);",

                //remove previous mean height first to bring back to 0 height
                "t.r -= t.a;",

                //propagate
                "vec2 du = vec2(uTexelSize.r, 0.0);",
                "vec2 dv = vec2(0.0, uTexelSize.g);",
                "t.b = 0.5 * (texture2D(uTexture,vUv+du).r",
                               "+ texture2D(uTexture,vUv-du).r",
                               "+ texture2D(uTexture,vUv+dv).r",
                               "+ texture2D(uTexture,vUv-dv).r - 4.0 * t.a) - t.b;",
                "t.b *= uDampingFactor;",

                //update
                "t.r = t.b;",

                //add new mean height
                "t.r += uMeanHeight;",

                //store new mean height
                "t.a = uMeanHeight;",

                //swap buffers
                "float temp = t.g;",
                "t.g = t.b;",
                "t.b = temp;",

                //write out to texture for next step
                "gl_FragColor = t;",
            "}"

        ].join('\n'),

        hfWater_tessendorfIWave_convolve: [

            //GPU version of "Interactive Water Surfaces" (Jerry Tessendorf, Game Programming Gems 4).
            //This is the convolution pre-pass to find the vertical derivative.

            //have to use #define here to get compile-time constant values,
            //otherwise there are problems in the double-for-loop and indexing into array.
            //remember to change this radius value after changing that in the GpuTessendorfIWaveWater class.
            "#define KERNEL_RADIUS 2",
            "#define KERNEL_WIDTH (2 * (KERNEL_RADIUS) + 1)",

            "uniform sampler2D uWaterTexture;",
            "uniform vec2 uTexelSize;",
            "uniform float uKernel[KERNEL_WIDTH * KERNEL_WIDTH];",

            "varying vec2 vUv;",

            "void main() {",

                //read water texture
                //r channel: height
                //g channel: prev height
                //b channel: vertical derivative
                //a channel: prev mean height
                "vec4 tWater = texture2D(uWaterTexture, vUv);",

                //propagate
                "tWater.b = 0.0;",
                "float fk, fl;",
                "vec4 tWaterNeighbour;",
                "for (int k = -KERNEL_RADIUS; k <= KERNEL_RADIUS; k++) {",
                    "fk = float(k);",
                    "for (int l = -KERNEL_RADIUS; l <= KERNEL_RADIUS; l++) {",
                        "fl = float(l);",
                        "tWaterNeighbour = texture2D(uWaterTexture, vec2(vUv.r + fk * uTexelSize.r, vUv.g + fl * uTexelSize.g));",
                        "tWater.b += uKernel[(k + KERNEL_RADIUS) * KERNEL_WIDTH + (l + KERNEL_RADIUS)] * (tWaterNeighbour.r - tWaterNeighbour.a);",
                    "}",
                "}",

                //write out to texture for next step
                "gl_FragColor = tWater;",
            "}"

        ].join('\n'),

        hfWater_tessendorfIWave: [

            //GPU version of "Interactive Water Surfaces" (Jerry Tessendorf, Game Programming Gems 4).
            //Need to run convolve fragment shader first before running this.

            //NOTE: I have added in mean height in the calculations, purely because of the flooding system.
            //It is not necessary if you do not need to rise the water level.

            "uniform sampler2D uWaterTexture;",
            "uniform float uTwoMinusDampTimesDt;",
            "uniform float uOnePlusDampTimesDt;",
            "uniform float uGravityTimesDtTimesDt;",
            "uniform float uMeanHeight;",

            "varying vec2 vUv;",

            "void main() {",

                //read water texture
                //r channel: height
                //g channel: prev height
                //b channel: vertical derivative
                //a channel: prev mean height
                "vec4 tWater = texture2D(uWaterTexture, vUv);",

                //remove previous mean height first to bring back to 0 height
                "tWater.r -= tWater.a;",

                //propagate
                "float temp = tWater.r;",
                "tWater.r = (tWater.r * uTwoMinusDampTimesDt",
                           "- tWater.g",
                           "- tWater.b * uGravityTimesDtTimesDt) / uOnePlusDampTimesDt;",
                "tWater.g = temp;",

                //add new mean height
                "tWater.r += uMeanHeight;",

                //store new mean height
                "tWater.a = uMeanHeight;",

                //write out to texture for next step
                "gl_FragColor = tWater;",
            "}"

        ].join('\n'),

        hfWater_pipeModel_calcFlux: [

            //GPU version of pipe model water.
            //This is the pre-pass to calculate flux.

            "uniform sampler2D uTerrainTexture;",
            "uniform sampler2D uWaterTexture;",
            "uniform sampler2D uFluxTexture;",
            "uniform sampler2D uStaticObstaclesTexture;",
            "uniform sampler2D uBoundaryTexture;",
            "uniform vec2 uTexelSize;",
            "uniform float uDampingFactor;",
            "uniform float uHeightToFluxFactor;",
            "uniform float uSegmentSizeSquared;",
            "uniform float uDt;",
            "uniform float uMinWaterHeight;",

            "varying vec2 vUv;",

            "void main() {",

                "vec2 du = vec2(uTexelSize.r, 0.0);",
                "vec2 dv = vec2(0.0, uTexelSize.g);",

                //read terrain texture
                //r channel: terrain height
                "vec4 tTerrain = texture2D(uTerrainTexture, vUv);",

                //read water texture
                //r channel: water height
                //g, b channels: vel
                //a channel: UNUSED
                "vec4 tWater = texture2D(uWaterTexture, vUv);",

                //read static obstacle texture
                //r channel: height
                "vec4 tObstacle = texture2D(uStaticObstaclesTexture, vUv);",

                "float waterHeight = tWater.r;",
                "float totalHeight = max(tTerrain.r, tObstacle.r) + waterHeight;",

                //read flux texture
                //r channel: fluxR
                //g channel: fluxL
                //b channel: fluxB
                //a channel: fluxT
                "vec4 tFlux = texture2D(uFluxTexture, vUv);",

                //calculate new flux
                "tFlux *= uDampingFactor;",
                "vec4 neighbourTotalHeights = vec4(texture2D(uWaterTexture, vUv + du).r + max(texture2D(uTerrainTexture, vUv + du).r, texture2D(uStaticObstaclesTexture, vUv + du).r),",
                                                  "texture2D(uWaterTexture, vUv - du).r + max(texture2D(uTerrainTexture, vUv - du).r, texture2D(uStaticObstaclesTexture, vUv - du).r),",
                                                  "texture2D(uWaterTexture, vUv - dv).r + max(texture2D(uTerrainTexture, vUv - dv).r, texture2D(uStaticObstaclesTexture, vUv - dv).r),",
                                                  "texture2D(uWaterTexture, vUv + dv).r + max(texture2D(uTerrainTexture, vUv + dv).r, texture2D(uStaticObstaclesTexture, vUv + dv).r));",
                "tFlux += (totalHeight - neighbourTotalHeights) * uHeightToFluxFactor;",
                "tFlux = max(vec4(0.0), tFlux);",

                //read boundary texture
                //r channel: fluxR
                //g channel: fluxL
                //b channel: fluxB
                //a channel: fluxT
                "vec4 tBoundary = texture2D(uBoundaryTexture, vUv);",

                //multiply flux with boundary texture to mask out fluxes
                "tFlux *= tBoundary;",

                //scale down outflow if it is more than available volume in the column
                "float currVol = (waterHeight - uMinWaterHeight) * uSegmentSizeSquared;",
                "float outVol = uDt * (tFlux.r + tFlux.g + tFlux.b + tFlux.a);",
                "tFlux *= min(1.0, currVol / outVol);",

                //write out to texture for next step
                "gl_FragColor = tFlux;",
            "}"

        ].join('\n'),

        hfWater_pipeModel: [

            //GPU version of pipe model water.
            //Need to run the flux calculation pre-pass first before running this.

            "uniform sampler2D uWaterTexture;",
            "uniform sampler2D uFluxTexture;",
            "uniform vec2 uTexelSize;",
            "uniform float uSegmentSize;",
            "uniform float uDt;",
            "uniform float uMinWaterHeight;",

            "varying vec2 vUv;",

            "void main() {",

                "vec2 du = vec2(uTexelSize.r, 0.0);",
                "vec2 dv = vec2(0.0, uTexelSize.g);",

                //read water texture
                //r channel: water height
                //g channel: horizontal velocity x
                //b channel: horizontal velocity z
                //a channel: UNUSED
                "vec4 tWater = texture2D(uWaterTexture, vUv);",

                //read flux textures
                //r channel: fluxR
                //g channel: fluxL
                //b channel: fluxB
                //a channel: fluxT
                "vec4 tFlux = texture2D(uFluxTexture, vUv);",
                "vec4 tFluxPixelLeft = texture2D(uFluxTexture, vUv-du);",
                "vec4 tFluxPixelRight = texture2D(uFluxTexture, vUv+du);",
                "vec4 tFluxPixelTop = texture2D(uFluxTexture, vUv+dv);",
                "vec4 tFluxPixelBottom = texture2D(uFluxTexture, vUv-dv);",

                "float avgWaterHeight = tWater.r;",

                //calculate new height
                "float fluxOut = tFlux.r + tFlux.g + tFlux.b + tFlux.a;",
                "float fluxIn = tFluxPixelLeft.r + tFluxPixelRight.g + tFluxPixelTop.b + tFluxPixelBottom.a;",
                "tWater.r += (fluxIn - fluxOut) * uDt / (uSegmentSize * uSegmentSize);",
                "tWater.r = max(uMinWaterHeight, tWater.r);",

                "avgWaterHeight = 0.5 * (avgWaterHeight + tWater.r);",  //this will get the average height of that from before and after the change

                //calculate horizontal velocities, from amount of water passing through per unit time
                "if (avgWaterHeight == 0.0) {",  //prevent division by 0
                    "tWater.g = 0.0;",
                    "tWater.b = 0.0;",
                "} else {",
                    "float threshold = float(tWater.r > 0.2);",  //0/1 threshold value for masking out weird velocities at terrain edges
                    "float segmentSizeTimesAvgWaterHeight = uSegmentSize * avgWaterHeight;",
                    "tWater.g = threshold * 0.5 * (tFluxPixelLeft.r - tFlux.g + tFlux.r - tFluxPixelRight.g) / segmentSizeTimesAvgWaterHeight;",
                    "tWater.b = threshold * 0.5 * (tFluxPixelTop.b - tFlux.a + tFlux.b - tFluxPixelBottom.a) / segmentSizeTimesAvgWaterHeight;",
                "}",

                //write out to texture for next step
                "gl_FragColor = tWater;",
            "}"

        ].join('\n'),

        setColor: [

            //Fragment shader to set colors on a render target

            "uniform vec4 uColor;",

            "void main() {",
                "gl_FragColor = uColor;",
            "}"

        ].join('\n'),

        setColorMasked: [

            //Fragment shader to set colors on specific channels while keeping the rest of the channels intact

            "uniform sampler2D uTexture;",
            "uniform vec4 uColor;",
            "uniform vec4 uChannelMask;",

            "varying vec2 vUv;",

            "void main() {",
                "vec4 t = texture2D(uTexture, vUv);",
                "gl_FragColor = (vec4(1.0) - uChannelMask) * t + uChannelMask * uColor;",
            "}"

        ].join('\n'),

        setSolidAlpha: [

            //Fragment shader that sets alpha for the given texture to 1.0

            "uniform sampler2D uTexture;",

            "varying vec2 vUv;",

            "void main() {",
                "gl_FragColor = vec4(texture2D(uTexture, vUv).rgb, 1.0);",
            "}"

        ].join('\n'),

        hfWater_obstacles_static: [

            //Fragment shader to calculate static obstacles texture

            "uniform sampler2D uObstacleTopTexture;",
            "uniform float uHalfRange;",

            "varying vec2 vUv;",

            "void main() {",

                //read texture for obstacle
                //r, g, b channels: depth (all these channels contain same value)
                //a channel: alpha
                "vec4 tTop = texture2D(uObstacleTopTexture, vUv);",

                //convert top value to world height
                "float topHeight = (uHalfRange - tTop.r) * tTop.a;",

                //write out to texture for next step
                "gl_FragColor = vec4(topHeight, 0.0, 0.0, 1.0);",
            "}"

        ].join('\n'),

        hfWater_obstacles_dynamic: [

            //Fragment shader to accumulate an obstacle texture

            "uniform sampler2D uObstaclesTexture;",
            "uniform sampler2D uObstacleTopTexture;",
            "uniform sampler2D uObstacleBottomTexture;",
            "uniform sampler2D uWaterTexture;",
            "uniform sampler2D uTerrainTexture;",

            "uniform float uHalfRange;",

            "varying vec2 vUv;",

            "void main() {",

                //read texture from previous step
                //r channel: whether in obstacle or not (accumulated)
                //g channel: height of water displaced (accumulated)
                //b channel: height of water displaced from previous step (accumulated)
                //a channel: height of water displaced (only for current rendered object)
                "vec4 t = texture2D(uObstaclesTexture, vUv);",

                //read texture for obstacle
                //r, g, b channels: depth (all these channels contain same value)
                //a channel: alpha
                "vec4 tTop = texture2D(uObstacleTopTexture, vUv);",
                "vec4 tBottom = texture2D(uObstacleBottomTexture, vec2(vUv.x, 1.0-vUv.y));",

                //read texture for water and terrain
                //r channel: height
                //other channels: other data which are not used here
                "vec4 tWater = texture2D(uWaterTexture, vUv);",
                "vec4 tTerrain = texture2D(uTerrainTexture, vUv);",
                "float waterHeight = tWater.r + tTerrain.r;",

                //convert top and bottom into same space (water plane at height of 0, upwards positive)
                "float bottomHeight = (tBottom.r - uHalfRange - waterHeight) * tBottom.a;",
                "float topHeight = (uHalfRange - waterHeight - tTop.r) * tTop.a;",

                //compare the top and bottom depths to determine if water is in obstacle
                "bool inObstacle = bottomHeight < 0.0 && topHeight > 0.0;",

                //also calculate amount of water displaced
                "float displacedHeight;",
                "if (bottomHeight > 0.0) {",
                    //totally above water, so there is no water displaced
                    "displacedHeight = 0.0;",
                "} else if (topHeight < 0.0) {",
                    //totally below water, so water displaced height is top minus bottom
                    "displacedHeight = topHeight - bottomHeight;",
                "} else {",
                    //partially submerged, so water displaced is water level minus bottom (which is just negative of bottom)
                    "displacedHeight = -bottomHeight;",
                "}",

                //write out to texture for next step
                "gl_FragColor = vec4(max(t.r, float(inObstacle)), t.g + displacedHeight, t.b, displacedHeight);",
            "}"

        ].join('\n'),

        encodeFloat: [

            //Fragment shader that encodes float value in input R channel to 4 unsigned bytes in output RGBA channels
            //Most of this code is from original GLSL codes from Piotr Janik, only slight modifications are done to fit the needs of this script
            //http://concord-consortium.github.io/lab/experiments/webgl-gpgpu/script.js
            //Using method 1 of the code.

            "uniform sampler2D uTexture;",
            "uniform vec4 uChannelMask;",

            "varying vec2 vUv;",

            "float shift_right(float v, float amt) {",
                "v = floor(v) + 0.5;",
                "return floor(v / exp2(amt));",
            "}",

            "float shift_left(float v, float amt) {",
                "return floor(v * exp2(amt) + 0.5);",
            "}",

            "float mask_last(float v, float bits) {",
                "return mod(v, shift_left(1.0, bits));",
            "}",

            "float extract_bits(float num, float from, float to) {",
                "from = floor(from + 0.5);",
                "to = floor(to + 0.5);",
                "return mask_last(shift_right(num, from), to - from);",
            "}",

            "vec4 encode_float(float val) {",

                "if (val == 0.0) {",
                    "return vec4(0.0, 0.0, 0.0, 0.0);",
                "}",

                "float sign = val > 0.0 ? 0.0 : 1.0;",
                "val = abs(val);",
                "float exponent = floor(log2(val));",
                "float biased_exponent = exponent + 127.0;",
                "float fraction = ((val / exp2(exponent)) - 1.0) * 8388608.0;",

                "float t = biased_exponent / 2.0;",
                "float last_bit_of_biased_exponent = fract(t) * 2.0;",
                "float remaining_bits_of_biased_exponent = floor(t);",

                "float byte4 = extract_bits(fraction, 0.0, 8.0) / 255.0;",
                "float byte3 = extract_bits(fraction, 8.0, 16.0) / 255.0;",
                "float byte2 = (last_bit_of_biased_exponent * 128.0 + extract_bits(fraction, 16.0, 23.0)) / 255.0;",
                "float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;",

                "return vec4(byte4, byte3, byte2, byte1);",
            "}",

            "void main() {",
                "vec4 t = texture2D(uTexture, vUv);",
                "gl_FragColor = encode_float(dot(t, uChannelMask));",
            "}"

        ].join('\n'),

        copyChannels: [

            //Fragment shader that copies data from one channel to another

            "uniform sampler2D uTexture;",
            "uniform vec4 uOriginChannelId;",
            "uniform vec4 uDestChannelId;",

            "varying vec2 vUv;",

            "void main() {",

                //read texture
                "vec4 t = texture2D(uTexture, vUv);",

                //get data from origin channel
                "float data = dot(t, uOriginChannelId);",

                //write to destination channel
                "gl_FragColor = (vec4(1.0) - uDestChannelId) * t + uDestChannelId * data;",
            "}"

        ].join('\n'),

        hfWater_calcDisturbMap: [

            //Fragment shader to calculate a water disturb map based on displaced heights from this frame and prev frame

            "uniform sampler2D uTexture;",
            "uniform vec2 uTexelSize;",

            "varying vec2 vUv;",

            "void main() {",

                "vec2 du = vec2(uTexelSize.r, 0.0);",
                "vec2 dv = vec2(0.0, uTexelSize.g);",

                //read textures
                //r channel: whether in obstacle or not (accumulated)
                //g channel: height of water displaced (accumulated)
                //b channel: height of water displaced from previous step (accumulated)
                //a channel: height of water displaced (only for current rendered object)
                "vec4 tLeft = texture2D(uTexture, vUv-du);",
                "vec4 tRight = texture2D(uTexture, vUv+du);",
                "vec4 tTop = texture2D(uTexture, vUv+dv);",
                "vec4 tBottom = texture2D(uTexture, vUv-dv);",

                //receive a quarter of displaced volume differences from neighbours
                "float result = 0.25 * ( (tLeft.g-tLeft.b) + (tRight.g-tRight.b) + (tTop.g-tTop.b) + (tBottom.g-tBottom.b) );",

                "gl_FragColor = vec4(result, -result, 0.0, 1.0);",  //g channel is there just to visualize negative displaced volumes
            "}"

        ].join('\n'),

        gaussianBlurX: [

            //Fragment shader gaussian blur (horizontal pass)
            //Largely obtained from:
            //http://www.gamerendering.com/2008/10/11/gaussian-blur-filter-shader/

            "uniform sampler2D uTexture;",
            "uniform float uTexelSize;",

            "varying vec2 vUv;",

            "void main() {",

                "vec4 sum = vec4(0.0);",

                // blur in x (horizontal)
                // take nine samples, with the distance uTexelSize between them
                "sum += texture2D(uTexture, vec2(vUv.x - 4.0 * uTexelSize, vUv.y)) * 0.05;",
                "sum += texture2D(uTexture, vec2(vUv.x - 3.0 * uTexelSize, vUv.y)) * 0.09;",
                "sum += texture2D(uTexture, vec2(vUv.x - 2.0 * uTexelSize, vUv.y)) * 0.12;",
                "sum += texture2D(uTexture, vec2(vUv.x - uTexelSize, vUv.y)) * 0.15;",
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y)) * 0.16;",
                "sum += texture2D(uTexture, vec2(vUv.x + uTexelSize, vUv.y)) * 0.15;",
                "sum += texture2D(uTexture, vec2(vUv.x + 2.0 * uTexelSize, vUv.y)) * 0.12;",
                "sum += texture2D(uTexture, vec2(vUv.x + 3.0 * uTexelSize, vUv.y)) * 0.09;",
                "sum += texture2D(uTexture, vec2(vUv.x + 4.0 * uTexelSize, vUv.y)) * 0.05;",

                "gl_FragColor = sum;",
            "}"

        ].join('\n'),

        gaussianBlurY: [

            //Fragment shader gaussian blur (vertical pass)
            //Largely obtained from:
            //http://www.gamerendering.com/2008/10/11/gaussian-blur-filter-shader/

            "uniform sampler2D uTexture;",
            "uniform float uTexelSize;",

            "varying vec2 vUv;",

            "void main() {",

                "vec4 sum = vec4(0.0);",

                // blur in y (vertical)
                // take nine samples, with the distance uTexelSize between them
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y - 4.0 * uTexelSize)) * 0.05;",
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y - 3.0 * uTexelSize)) * 0.09;",
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y - 2.0 * uTexelSize)) * 0.12;",
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y - uTexelSize)) * 0.15;",
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y)) * 0.16;",
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y + uTexelSize)) * 0.15;",
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y + 2.0 * uTexelSize)) * 0.12;",
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y + 3.0 * uTexelSize)) * 0.09;",
                "sum += texture2D(uTexture, vec2(vUv.x, vUv.y + 4.0 * uTexelSize)) * 0.05;",

                "gl_FragColor = sum;",
            "}"

        ].join('\n'),

        combineTextures: [

            //Fragment shader to combine textures

            "uniform sampler2D uTexture1;",
            "uniform sampler2D uTexture2;",

            "varying vec2 vUv;",

            "void main() {",
                "gl_FragColor = texture2D(uTexture1, vUv) + texture2D(uTexture2, vUv);",
            "}"

        ].join('\n'),

        combineTexturesMask: [

            //Fragment shader to combine textures: multiply texture1 with alpha channel of texture2

            "uniform sampler2D uTexture1;",
            "uniform sampler2D uTexture2;",  //for alpha channel

            "varying vec2 vUv;",

            "void main() {",

                //read textures
                "vec4 t1 = texture2D(uTexture1, vUv);",
                "vec4 t2 = texture2D(uTexture2, vUv);",

                //multiply all channels of t1 with alpha of t2
                "t1 *= t2.a;",

                "gl_FragColor = t1;",
            "}"

        ].join('\n'),

        erode: [

            //Fragment shader erode. This is just a simple 1-pixel erosion based on min.

            "uniform sampler2D uTexture;",
            "uniform float uTexelSize;",

            "varying vec2 vUv;",

            "void main() {",

                "vec2 du = vec2(uTexelSize, 0.0);",
                "vec2 dv = vec2(0.0, uTexelSize);",

                //get current and neighbour pixel values
                "float curr = texture2D(uTexture, vUv).r;",
                "float right = texture2D(uTexture, vUv + du).r;",
                "float left = texture2D(uTexture, vUv - du).r;",
                "float bottom = texture2D(uTexture, vUv - dv).r;",
                "float top = texture2D(uTexture, vUv + dv).r;",

                //take min
                "float result = min(curr, min(right, min(left, min(bottom, top))));",

                "gl_FragColor = vec4(result, 0.0, 0.0, 1.0);",
            "}"

        ].join('\n'),

        depth: [

            //Fragment shader to set RGB colors based on depth.
            //The one that comes with Three.js is clamped to 1 and is non-linear, so I have to create my own version.

            "uniform float uNear;",
            "uniform float uFar;",

            "void main() {",
                "float color = mix(uFar, uNear, gl_FragCoord.z/gl_FragCoord.w);",
                "gl_FragColor = vec4(vec3(color), 1.0);",
            "}"

        ].join('\n'),

        hfWater_pipeModel_calcFinalWaterHeight: [

            //Fragment shader to combine textures

            "uniform sampler2D uTerrainTexture;",
            "uniform sampler2D uStaticObstaclesTexture;",
            "uniform sampler2D uWaterTexture;",
            "uniform sampler2D uMultiplyTexture;",  //texture to multiply the results of uTerrainTexture + uStaticObstaclesTexture
            "uniform float uMaskOffset;",  //using uMultiplyTexture as a mask to offset the 0 regions

            "varying vec2 vUv;",

            "void main() {",

                "vec4 t = max(texture2D(uTerrainTexture, vUv), texture2D(uStaticObstaclesTexture, vUv)) + texture2D(uWaterTexture, vUv);",

                //read multiply texture and multiply
                "vec4 tMultiply = texture2D(uMultiplyTexture, vUv);",
                "t *= tMultiply;",

                //do offset with masking
                "t += (1.0 - tMultiply) * uMaskOffset;",

                "gl_FragColor = t;",
            "}"

        ].join('\n')

    }

};
SKUNAMI.GpuHeightFieldWater.prototype.__setupShaders = function () {

    this.__disturbAndSourceMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture },
            uStaticObstaclesTexture: { type: 't', value: this.__emptyTexture },
            uDisturbTexture: { type: 't', value: this.__emptyTexture },
            uUseObstacleTexture: { type: 'i', value: 1 },  //turn on by default for most of the surface water types to use (pipe model will not need this)
            uIsDisturbing: { type: 'i', value: 0 },
            uDisturbPos: { type: 'v2', value: new THREE.Vector2(0.5, 0.5) },
            uDisturbAmount: { type: 'f', value: this.__disturbAmount },
            uDisturbRadius: { type: 'f', value: this.__disturbRadius },
            uIsSourcing: { type: 'i', value: 0 },
            uSourcePos: { type: 'v2', value: new THREE.Vector2(0.5, 0.5) },
            uSourceAmount: { type: 'f', value: this.__sourceAmount },
            uSourceRadius: { type: 'f', value: this.__sourceRadius },
            uIsFlooding: { type: 'i', value: 0 },  //for pipe model water only
            uFloodAmount: { type: 'f', value: 0 }  //for pipe model water only
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['hfWater_disturb']
    });

    this.__waterSimMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture },
            uTexelSize: { type: 'v2', value: new THREE.Vector2(this.__texelSize, this.__texelSize) },
            uTexelWorldSize: { type: 'v2', value: new THREE.Vector2(this.__size / this.__res, this.__size / this.__res) },
            uDampingFactor: { type: 'f', value: this.__dampingFactor },
            uDt: { type: 'f', value: 0.0 },
            uMeanHeight: { type: 'f', value: this.__meanHeight }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__getWaterFragmentShaderContent()
    });

    this.__resetMaskedMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture },
            uColor: { type: 'v4', value: new THREE.Vector4() },
            uChannelMask: { type: 'v4', value: new THREE.Vector4() }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['setColorMasked']
    });

    this.__setSolidAlphaMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['setSolidAlpha']
    });

    this.__staticObstaclesMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uObstacleTopTexture: { type: 't', value: this.__emptyTexture },
            uHalfRange: { type: 'f', value: this.__rttObstaclesCameraRange / 2.0 }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['hfWater_obstacles_static']
    });

    this.__dynObstaclesMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uObstaclesTexture: { type: 't', value: this.__emptyTexture },
            uObstacleTopTexture: { type: 't', value: this.__emptyTexture },
            uObstacleBottomTexture: { type: 't', value: this.__emptyTexture },
            uWaterTexture: { type: 't', value: this.__emptyTexture },
            uTerrainTexture: { type: 't', value: this.__emptyTexture },
            uHalfRange: { type: 'f', value: this.__rttObstaclesCameraRange / 2.0 }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['hfWater_obstacles_dynamic']
    });

    this.__rttEncodeFloatMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture },
            uChannelMask: { type: 'v4', value: new THREE.Vector4() }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['encodeFloat']
    });

    this.__copyChannelsMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture },
            uOriginChannelId: { type: 'v4', value: new THREE.Vector4() },
            uDestChannelId: { type: 'v4', value: new THREE.Vector4() }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['copyChannels']
    });

    this.__calcDisturbMapMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture },
            uTexelSize: { type: 'v2', value: new THREE.Vector2(this.__texelSize, this.__texelSize) }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['hfWater_calcDisturbMap']
    });

    this.__gaussianBlurXMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture },
            uTexelSize: { type: 'f', value: this.__texelSize }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['gaussianBlurX']
    });

    this.__gaussianBlurYMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture },
            uTexelSize: { type: 'f', value: this.__texelSize }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['gaussianBlurY']
    });

    this.__combineTexturesMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture1: { type: 't', value: this.__emptyTexture },
            uTexture2: { type: 't', value: this.__emptyTexture }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['combineTextures']
    });

    this.__erodeMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: this.__emptyTexture },
            uTexelSize: { type: 'f', value: this.__texelSize }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['erode']
    });

    this.__channelVectors = {
        'r': new THREE.Vector4(1.0, 0.0, 0.0, 0.0),
        'g': new THREE.Vector4(0.0, 1.0, 0.0, 0.0),
        'b': new THREE.Vector4(0.0, 0.0, 1.0, 0.0),
        'a': new THREE.Vector4(0.0, 0.0, 0.0, 1.0)
    };
};
//Sets up the render-to-texture scene (2 render targets by default)
SKUNAMI.GpuHeightFieldWater.prototype.__setupRttScene = function () {

    //create a RTT scene
    this.__rttScene = new THREE.Scene();

    //create an orthographic RTT camera
    var far = 10000;
    var near = -far;
    this.__rttCamera = new THREE.OrthographicCamera(-this.__halfSize, this.__halfSize, this.__halfSize, -this.__halfSize, near, far);

    //create a quad which we will use to invoke the shaders
    this.__rttQuadGeom = new THREE.PlaneGeometry(this.__size, this.__size);
    this.__rttQuadMesh = new THREE.Mesh(this.__rttQuadGeom, this.__waterSimMaterial);
    this.__rttScene.add(this.__rttQuadMesh);
};
SKUNAMI.GpuHeightFieldWater.prototype.__setupRttRenderTargets = function () {
    //create RTT render targets (need two for feedback)
    if (this.__supportsTextureFloatLinear) {
        this.__rttRenderTarget1 = new THREE.WebGLRenderTarget(this.__res, this.__res, this.__linearFloatRgbaParams);
    } else {
        this.__rttRenderTarget1 = new THREE.WebGLRenderTarget(this.__res, this.__res, this.__nearestFloatRgbaParams);
    }
    this.__rttRenderTarget1.generateMipmaps = false;
    this.__clearRenderTarget(this.__rttRenderTarget1, 0.0, 0.0, 0.0, 0.0);  //clear render target (necessary for FireFox)
    this.__rttRenderTarget2 = this.__rttRenderTarget1.clone();
    this.__clearRenderTarget(this.__rttRenderTarget2, 0.0, 0.0, 0.0, 0.0);  //clear render target (necessary for FireFox)

    //create render targets purely for display purposes
    this.__rttWaterDisplay = this.__rttRenderTarget1.clone();
    this.__clearRenderTarget(this.__rttWaterDisplay, 0.0, 0.0, 0.0, 1.0);  //clear render target (necessary for FireFox)
    this.__rttObstaclesDisplay = this.__rttRenderTarget1.clone();
    this.__clearRenderTarget(this.__rttObstaclesDisplay, 0.0, 0.0, 0.0, 1.0);  //clear render target (necessary for FireFox)

    //create another RTT render target encoding float to 4-byte data
    this.__rttFloatEncoderRenderTarget = new THREE.WebGLRenderTarget(this.__res, this.__res, this.__nearestFloatRgbaParams);
    this.__rttFloatEncoderRenderTarget.generateMipmaps = false;
    this.__clearRenderTarget(this.__rttFloatEncoderRenderTarget, 0.0, 0.0, 0.0, 0.0);  //clear render target (necessary for FireFox)

    //some render targets for blurred textures
    this.__rttCombinedHeightsBlurredRenderTarget = this.__rttRenderTarget1.clone();
    this.__clearRenderTarget(this.__rttCombinedHeightsBlurredRenderTarget, 0.0, 0.0, 0.0, 0.0);  //clear render target (necessary for FireFox)
    this.__rttDynObstaclesBlurredRenderTarget = this.__rttRenderTarget1.clone();
    this.__clearRenderTarget(this.__rttDynObstaclesBlurredRenderTarget, 0.0, 0.0, 0.0, 0.0);  //clear render target (necessary for FireFox)

    //create render target for storing the disturbed map (due to interaction with rigid bodes)
    this.__rttDisturbMapRenderTarget = this.__rttRenderTarget1.clone();
    this.__clearRenderTarget(this.__rttDisturbMapRenderTarget, 0.0, 0.0, 0.0, 0.0);  //clear render target (necessary for FireFox)
};
SKUNAMI.GpuHeightFieldWater.prototype.__clearRenderTarget = function (renderTarget, r, g, b, a) {
    this.__rttQuadMesh.material = this.__resetMaterial;
    this.__resetMaterial.uniforms['uColor'].value.set(r, g, b, a);
    this.__renderer.render(this.__rttScene, this.__rttCamera, renderTarget, false);
};
//Sets up the vertex-texture-fetch for the given mesh
SKUNAMI.GpuHeightFieldWater.prototype.__setupVtf = function () {
    this.__mesh.material = new THREE.ShaderMaterial({
        uniforms: THREE.UniformsUtils.merge([
            THREE.UniformsLib['lights'],
            THREE.UniformsLib['shadowmap'],
            {
                uTexture: { type: 't', value: this.__rttRenderTarget1 },
                uTexelSize: { type: 'v2', value: new THREE.Vector2(this.__texelSize, this.__texelSize) },
                uTexelWorldSize: { type: 'v2', value: new THREE.Vector2(this.__segmentSize, this.__segmentSize) },
                uHeightMultiplier: { type: 'f', value: 1.0 },
                uBaseColor: { type: 'v3', value: new THREE.Vector3(0.45, 0.95, 1.0) }
            }
        ]),
        vertexShader: this.__shaders.vert['heightMap'],
        fragmentShader: this.__shaders.frag['lambert'],
        lights: true
    });
};
//Checks for WebGL extensions. Checks for OES_texture_float_linear and vertex texture fetch capability by default.
SKUNAMI.GpuHeightFieldWater.prototype.__checkExtensions = function (renderer) {
    var context = this.__renderer.context;

    //determine floating point texture support
    //https://www.khronos.org/webgl/public-mailing-list/archives/1306/msg00002.html

    //get floating point texture support
    if (!context.getExtension('OES_texture_float')) {
        var msg = 'No support for floating point textures. Extension not available: OES_texture_float';
        alert(msg);
        throw new Error(msg);
    }

    //get floating point linear filtering support
    this.__supportsTextureFloatLinear = context.getExtension('OES_texture_float_linear') !== null;
    console.log('Texture float linear filtering support: ' + this.__supportsTextureFloatLinear);

    //get vertex texture support
    if (!context.getParameter(context.MAX_VERTEX_TEXTURE_IMAGE_UNITS)) {
        var msg = 'Vertex textures not supported on your graphics card';
        alert(msg);
        throw new Error(msg);
    }
};
SKUNAMI.GpuHeightFieldWater.prototype.__initDataAndTextures = function () {

    var i, j, len, idx;

    //init everything to 1 first
    for (i = 0, len = this.__boundaryData.length; i < len; i++) {
        this.__boundaryData[i] = 1.0;
    }

    //init all boundary values to 0
    j = 0;
    for (i = 0; i < this.__res; i++) {
        idx = 4 * (i + this.__res * j);
        this.__boundaryData[idx] = 0.0;
        this.__boundaryData[idx + 1] = 0.0;
        this.__boundaryData[idx + 2] = 0.0;
        this.__boundaryData[idx + 3] = 0.0;
    }
    j = this.__res - 1;
    for (i = 0; i < this.__res; i++) {
        idx = 4 * (i + this.__res * j);
        this.__boundaryData[idx] = 0.0;
        this.__boundaryData[idx + 1] = 0.0;
        this.__boundaryData[idx + 2] = 0.0;
        this.__boundaryData[idx + 3] = 0.0;
    }
    i = 0;
    for (j = 0; j < this.__res; j++) {
        idx = 4 * (i + this.__res * j);
        this.__boundaryData[idx] = 0.0;
        this.__boundaryData[idx + 1] = 0.0;
        this.__boundaryData[idx + 2] = 0.0;
        this.__boundaryData[idx + 3] = 0.0;
    }
    i = this.__res - 1;
    for (j = 0; j < this.__res; j++) {
        idx = 4 * (i + this.__res * j);
        this.__boundaryData[idx] = 0.0;
        this.__boundaryData[idx + 1] = 0.0;
        this.__boundaryData[idx + 2] = 0.0;
        this.__boundaryData[idx + 3] = 0.0;
    }

    //finally assign data to texture
    this.__boundaryTexture.image.data = this.__boundaryData;
    this.__boundaryTexture.needsUpdate = true;
};
SKUNAMI.GpuHeightFieldWater.prototype.__setupObstaclesScene = function () {

    //create top and bottom cameras
    this.__rttObstaclesTopCamera = new THREE.OrthographicCamera(-this.__halfSize, this.__halfSize, -this.__halfSize, this.__halfSize, 0, this.__rttObstaclesCameraRange);
    this.__rttObstaclesTopCamera.position.y = -this.__rttObstaclesCameraRange / 2;
    this.__rttObstaclesTopCamera.rotation.x = THREE.Math.degToRad(90);
    this.__rttObstaclesBottomCamera = new THREE.OrthographicCamera(-this.__halfSize, this.__halfSize, -this.__halfSize, this.__halfSize, 0, this.__rttObstaclesCameraRange);
    this.__rttObstaclesBottomCamera.position.y = this.__rttObstaclesCameraRange / 2;
    this.__rttObstaclesBottomCamera.rotation.x = THREE.Math.degToRad(-90);

    //create obstacles render targets and two more for top and bottom views
    this.__rttStaticObstaclesRenderTarget = this.__rttRenderTarget1.clone();
    this.__rttDynObstaclesRenderTarget = this.__rttRenderTarget1.clone();
    this.__rttObstacleTopRenderTarget = this.__rttRenderTarget1.clone();
    this.__rttObstacleBottomRenderTarget = this.__rttRenderTarget1.clone();

    //create render target for masking out water areas based on obstacle's alpha
    this.__rttMaskedWaterRenderTarget = this.__rttRenderTarget1.clone();

    //create material for rendering the obstacles
    this.__rttObstaclesDepthMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uNear: { type: 'f', value: 0 },
            uFar: { type: 'f', value: this.__rttObstaclesCameraRange }
        },
        vertexShader: this.__shaders.vert['pass'],
        fragmentShader: this.__shaders.frag['depth']
    });

    //create material for masking out water texture
    this.__maskWaterMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture1: { type: 't', value: this.__emptyTexture },
            uTexture2: { type: 't', value: this.__emptyTexture }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['combineTexturesMask']
    });
};
/**
 * Resets the simulation
 */
SKUNAMI.GpuHeightFieldWater.prototype.reset = function () {
    this.__initCounter = 5;
};
SKUNAMI.GpuHeightFieldWater.prototype.__resetPass = function () {
    //reset height in main render target
    this.__clearRenderTarget(this.__rttRenderTarget2, this.__meanHeight, 0, 0, this.__meanHeight);
    this.__swapRenderTargets();
};
/**
 * Disturbs the water, causing ripples on the water surface
 * @param  {THREE.Vector3} position World-space position to disturb at
 * @param  {number} amount Amount of water to disturb
 * @param  {number} radius Radius of disturb
 */
SKUNAMI.GpuHeightFieldWater.prototype.disturb = function (position, amount, radius) {
    this.__isDisturbing = true;
    this.__disturbUvPos.x = (position.x + this.__halfSize) / this.__size;
    this.__disturbUvPos.y = (position.z + this.__halfSize) / this.__size;
    this.__disturbAmount = amount;
    this.__disturbRadius = radius;
};
/**
 * Floods the scene by the given volume
 * @abstract
 * @param  {number} volume Volume of water to flood the scene with, in cubic scene units
 */
SKUNAMI.GpuHeightFieldWater.prototype.flood = function (volume) {
    throw new Error('Abstract method not implemented');
};
SKUNAMI.GpuHeightFieldWater.prototype.__disturbPass = function () {
    var shouldRender = false;
    if (this.__disturbMapHasUpdated) {
        // this.__disturbAndSourceMaterial.uniforms['uStaticObstaclesTexture'].value = this.__rttDynObstaclesRenderTarget;
        this.__disturbAndSourceMaterial.uniforms['uDisturbTexture'].value = this.__rttDisturbMapRenderTarget;
        shouldRender = true;
    }
    if (this.__isDisturbing && this.__disturbAmount !== 0.0) {
        // this.__disturbAndSourceMaterial.uniforms['uStaticObstaclesTexture'].value = this.__rttStaticObstaclesRenderTarget;
        this.__disturbAndSourceMaterial.uniforms['uIsDisturbing'].value = this.__isDisturbing;
        this.__disturbAndSourceMaterial.uniforms['uDisturbPos'].value.copy(this.__disturbUvPos);
        this.__disturbAndSourceMaterial.uniforms['uDisturbAmount'].value = this.__disturbAmount;
        this.__disturbAndSourceMaterial.uniforms['uDisturbRadius'].value = this.__disturbRadius / this.__size;
        shouldRender = true;
    }
    if (shouldRender) {
        this.__rttQuadMesh.material = this.__disturbAndSourceMaterial;
        this.__disturbAndSourceMaterial.uniforms['uTexture'].value = this.__rttRenderTarget2;
        this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTarget1, false);
        this.__swapRenderTargets();

        this.__isDisturbing = false;
        this.__rttQuadMesh.material.uniforms['uIsDisturbing'].value = false;
    }
};
SKUNAMI.GpuHeightFieldWater.prototype.__waterSimPass = function (substepDt) {
    this.__rttQuadMesh.material = this.__waterSimMaterial;
    this.__waterSimMaterial.uniforms['uTexture'].value = this.__rttRenderTarget2;
    this.__waterSimMaterial.uniforms['uDt'].value = substepDt;
    this.__waterSimMaterial.uniforms['uMeanHeight'].value = this.__meanHeight;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTarget1, false);
    this.__swapRenderTargets();
};
SKUNAMI.GpuHeightFieldWater.prototype.__displayPass = function () {
    if (this.__shouldDisplayWaterTexture) {
        this.__rttQuadMesh.material = this.__setSolidAlphaMaterial;
        this.__setSolidAlphaMaterial.uniforms['uTexture'].value = this.__rttRenderTarget2;
        this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttWaterDisplay, false);
        this.__swapRenderTargets();
    }
    if (this.__shouldDisplayObstaclesTexture) {
        this.__rttQuadMesh.material = this.__setSolidAlphaMaterial;
        this.__setSolidAlphaMaterial.uniforms['uTexture'].value = this.__rttDynObstaclesBlurredRenderTarget;
        this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttObstaclesDisplay, false);
        this.__swapRenderTargets();
    }
};
SKUNAMI.GpuHeightFieldWater.prototype.__calculateSubsteps = function (dt) {
    return 1;
};
/**
 * Updates the water simulation<br/><strong>NOTE:  This needs to be called every frame, after renderer.clear() and before renderer.render(...)</strong>
 * @param  {number} dt Elapsed time since previous frame
 */
SKUNAMI.GpuHeightFieldWater.prototype.update = function (dt) {

    //NOTE: unable to figure out why cannot clear until a few updates later,
    //so using this dirty hack to init for a few frames
    if (this.__initCounter > 0) {
        this.__resetPass();
        this.__initCounter -= 1;
        return;
    }

    //fix dt for the moment (better to be in slow-mo in extreme cases than to explode)
    dt = 1.0 / 60.0;

    //update static obstacle texture
    if (this.__shouldUpdateStaticObstacle) {
        this.__updateStaticObstacleTexture(dt);
        this.__shouldUpdateStaticObstacle = false;
    }

    //update dynamic obstacle textures
    if (this.__dynObstacles.length > 0) {
        this.__updateDynObstacleTexture(dt);
    }

    //do multiple full steps per frame to speed up some of algorithms that are slow to propagate at high mesh resolutions
    var i;
    for (i = 0; i < this.__multisteps; i++) {
        this.__step(dt);
    }

    //post step
    this.__postStepPass();

    //display pass
    this.__displayPass();
};
SKUNAMI.GpuHeightFieldWater.prototype.__step = function (dt) {

    //calculate the number of substeps needed
    var substeps = this.__calculateSubsteps(dt);
    var substepDt = dt / substeps;

    //disturb
    this.__disturbPass();

    //water sim
    var i;
    for (i = 0; i < substeps; i++) {
        this.__waterSimPass(substepDt);
    }
};
SKUNAMI.GpuHeightFieldWater.prototype.__postStepPass = function () {
    //rebind render target to water mesh to ensure vertex shader gets the right texture
    this.__mesh.material.uniforms['uTexture'].value = this.__rttRenderTarget1;
};
SKUNAMI.GpuHeightFieldWater.prototype.__swapRenderTargets = function () {
    var temp = this.__rttRenderTarget1;
    this.__rttRenderTarget1 = this.__rttRenderTarget2;
    this.__rttRenderTarget2 = temp;
    // this.__rttQuadMesh.material.uniforms['uTexture'].value = this.__rttRenderTarget2;
};
/**
 * Adds a static obstacle into the system
 * @param {THREE.Mesh} mesh Mesh to use as a static obstacle
 */
SKUNAMI.GpuHeightFieldWater.prototype.addStaticObstacle = function (mesh) {
    if (!(mesh instanceof THREE.Mesh)) {
        throw new Error('mesh must be of type THREE.Mesh');
    }

    if (!mesh.__skunami) {
        mesh.__skunami = {};
    }
    mesh.__skunami.isObstacle = true;
    mesh.__skunami.isDynamic = false;
    mesh.__skunami.mass = 0;
    this.__staticObstacles.push(mesh);

    //set a flag to indicate that we want to update static obstacle texture during update() call
    this.__shouldUpdateStaticObstacle = true;
};
/**
 * Adds a dynamic obstacle into the system
 * @param {THREE.Mesh} mesh Mesh to use as a dynamic obstacle
 * @param {number} mass Mass of the dynamic obstacle
 */
SKUNAMI.GpuHeightFieldWater.prototype.addDynamicObstacle = function (mesh, mass) {
    if (!(mesh instanceof THREE.Mesh)) {
        throw new Error('mesh must be of type THREE.Mesh');
    }
    if (typeof mass === 'undefined') {
        throw new Error('mass not specified');
    }
    if (!mesh.__skunami) {
        mesh.__skunami = {};
    }
    mesh.__skunami.isObstacle = true;
    mesh.__skunami.isDynamic = true;
    mesh.__skunami.mass = mass;
    this.__dynObstacles.push(mesh);
};
/**
 * Removes obstacle from the system
 * @param  {THREE.Mesh} mesh Mesh of the obstacle to remove
 */
SKUNAMI.GpuHeightFieldWater.prototype.removeObstacle = function (mesh) {

    //remove from dynamic obstacle array if it exists
    var i, len;
    for (i = 0, len = this.__dynObstacles.length; i < len; i++) {
        if (this.__dynObstacles[i] === mesh) {
            this.__dynObstacles.splice(i, 1);
        }
    }

    //remove from static obstacle array if it exists
    var isStaticObstacle = false;
    for (i = 0, len = this.__staticObstacles.length; i < len; i++) {
        if (this.__staticObstacles[i] === mesh) {
            this.__staticObstacles.splice(i, 1);
            isStaticObstacle = true;
        }
    }
    if (isStaticObstacle) {
        //set a flag to indicate that we want to update static obstacle texture during update() call
        this.__shouldUpdateStaticObstacle = true;
    }
};
//This should only be called during update() call. Should not be called directly.
SKUNAMI.GpuHeightFieldWater.prototype.__updateStaticObstacleTexture = function (dt) {

    //static obstacle map just needs the top height (like the terrain)

    //clear obstacle texture first
    this.__clearRenderTarget(this.__rttStaticObstaclesRenderTarget, 0.0, 0.0, 0.0, 1.0);  //set unused alpha channel to 1 so that we can see the result
    this.__clearRenderTarget(this.__rttObstacleTopRenderTarget, 0.0, 0.0, 0.0, 0.0);

    var that = this;

    //hide and reset everything in scene
    this.__scene.traverse(function (object) {
        object.visibleStore = object.visible;
        object.visible = false;
    });

    //set an override depth map material for the scene
    this.__scene.overrideMaterial = this.__rttObstaclesDepthMaterial;

    //show all static obstacles
    var i, len;
    for (i = 0, len = this.__staticObstacles.length; i < len; i++) {
        this.__staticObstacles[i].visible = true;
    }

    //render from the top view to get the top height
    this.__renderer.render(this.__scene, this.__rttObstaclesTopCamera, this.__rttObstacleTopRenderTarget, false);

    //process this depth actual height
    this.__rttQuadMesh.material = this.__staticObstaclesMaterial;
    this.__staticObstaclesMaterial.uniforms['uObstacleTopTexture'].value = this.__rttObstacleTopRenderTarget;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttStaticObstaclesRenderTarget, false);

    //erode the map so that the water heights won't show at the sides of the obstacles
    this.__rttQuadMesh.material = this.__erodeMaterial;
    this.__erodeMaterial.uniforms['uTexture'].value = this.__rttStaticObstaclesRenderTarget;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttStaticObstaclesRenderTarget, false);
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttStaticObstaclesRenderTarget, false);

    //remove scene override material
    this.__scene.overrideMaterial = null;

    //restore visibility in the scene
    this.__scene.traverse(function (object) {
        object.visible = object.visibleStore;
    });
};
SKUNAMI.GpuHeightFieldWater.prototype.__updateDynObstacleTexture = function (dt) {

    //store accumulated displaced height channel from previous frame first (by copying G channel to B channel)
    this.__rttQuadMesh.material = this.__copyChannelsMaterial;
    this.__copyChannelsMaterial.uniforms['uTexture'].value = this.__rttDynObstaclesRenderTarget;
    this.__copyChannelsMaterial.uniforms['uOriginChannelId'].value.copy(this.__channelVectors.g);
    this.__copyChannelsMaterial.uniforms['uDestChannelId'].value.copy(this.__channelVectors.b);
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttDynObstaclesRenderTarget, false);

    //clear obstacle textures
    this.__rttQuadMesh.material = this.__resetMaskedMaterial;
    this.__resetMaskedMaterial.uniforms['uTexture'].value = this.__rttDynObstaclesRenderTarget;
    this.__resetMaskedMaterial.uniforms['uColor'].value.set(0.0, 0.0, 0.0, 0.0);
    this.__resetMaskedMaterial.uniforms['uChannelMask'].value.set(1.0, 1.0, 0.0, 1.0);  //don't clear B channel which stores previous displaced vol
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttDynObstaclesRenderTarget, false);

    //combine water and terrain heights into one and then blur it
    this.__rttQuadMesh.material = this.__combineTexturesMaterial;
    this.__combineTexturesMaterial.uniforms['uTexture1'].value = this.__rttRenderTarget2;
    this.__combineTexturesMaterial.uniforms['uTexture2'].value = this.__terrainTexture;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttCombinedHeightsBlurredRenderTarget, false);
    this.__rttQuadMesh.material = this.__gaussianBlurXMaterial;
    this.__gaussianBlurXMaterial.uniforms['uTexture'].value = this.__rttCombinedHeightsBlurredRenderTarget;
    this.__gaussianBlurXMaterial.uniforms['uTexelSize'].value = 1.0 / this.__res;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttCombinedHeightsBlurredRenderTarget, false);
    this.__rttQuadMesh.material = this.__gaussianBlurYMaterial;
    this.__gaussianBlurYMaterial.uniforms['uTexture'].value = this.__rttCombinedHeightsBlurredRenderTarget;
    this.__gaussianBlurYMaterial.uniforms['uTexelSize'].value = 1.0 / this.__res;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttCombinedHeightsBlurredRenderTarget, false);

    var that = this;

    //hide and reset everything in scene
    this.__scene.traverse(function (object) {
        object.visibleStore = object.visible;
        object.visible = false;
    });

    //set an override depth map material for the scene
    this.__scene.overrideMaterial = this.__rttObstaclesDepthMaterial;

    //render top & bottom of each obstacle and compare to current water texture
    this.__scene.traverse(function (object) {
        if (object instanceof THREE.Mesh && object.__skunami && object.__skunami.isObstacle && object.__skunami.isDynamic) {

            //show current mesh
            object.visible = true;

            //clear top and bottom render targets
            that.__clearRenderTarget(that.__rttObstacleTopRenderTarget, 0.0, 0.0, 0.0, 0.0);
            that.__clearRenderTarget(that.__rttObstacleBottomRenderTarget, 0.0, 0.0, 0.0, 0.0);

            //render top and bottom depth maps
            that.__renderer.render(that.__scene, that.__rttObstaclesTopCamera, that.__rttObstacleTopRenderTarget, false);
            that.__renderer.render(that.__scene, that.__rttObstaclesBottomCamera, that.__rttObstacleBottomRenderTarget, false);

            //update obstacle texture
            that.__rttQuadMesh.material = that.__dynObstaclesMaterial;
            that.__dynObstaclesMaterial.uniforms['uObstaclesTexture'].value = that.__rttDynObstaclesRenderTarget;
            that.__dynObstaclesMaterial.uniforms['uObstacleTopTexture'].value = that.__rttObstacleTopRenderTarget;
            that.__dynObstaclesMaterial.uniforms['uObstacleBottomTexture'].value = that.__rttObstacleBottomRenderTarget;
            that.__dynObstaclesMaterial.uniforms['uWaterTexture'].value = that.__rttCombinedHeightsBlurredRenderTarget;  //use blurred heights
            that.__dynObstaclesMaterial.uniforms['uTerrainTexture'].value = that.__emptyTexture;
            that.__renderer.render(that.__rttScene, that.__rttCamera, that.__rttDynObstaclesRenderTarget, false);

            //if object is dynamic, store additional info
            // if (object.__skunami.isDynamic) {

            //TODO: reduce the number of texture reads to speed up (getPixels() is very expensive)

            //find total water volume displaced by this object (from A channel data)
            that.__pr.reduce(that.__rttDynObstaclesRenderTarget, 'sum', 'a');
            object.__skunami.totalDisplacedVol = that.__pr.getPixelFloatData('a')[0] * that.__segmentSizeSquared;  //cubic metres

            //mask out velocity field using object's alpha
            that.__rttQuadMesh.material = that.__maskWaterMaterial;
            that.__maskWaterMaterial.uniforms['uTexture1'].value = that.__rttRenderTarget1;
            that.__maskWaterMaterial.uniforms['uTexture2'].value = that.__rttObstacleTopRenderTarget;
            that.__renderer.render(that.__rttScene, that.__rttCamera, that.__rttMaskedWaterRenderTarget, false);

            //find total horizontal velocities affecting this object
            that.__pr.reduce(that.__rttMaskedWaterRenderTarget, 'sum', 'g');
            object.__skunami.totalVelocityX = that.__pr.getPixelFloatData('g')[0];
            that.__pr.reduce(that.__rttMaskedWaterRenderTarget, 'sum', 'b');
            object.__skunami.totalVelocityZ = that.__pr.getPixelFloatData('b')[0];

            //calculate total area covered by this object
            that.__pr.reduce(that.__rttObstacleTopRenderTarget, 'sum', 'a');
            object.__skunami.totalArea = that.__pr.getPixelFloatData('a')[0];

            //calculate average velocities affecting this object
            if (object.__skunami.totalArea === 0.0) {
                object.__skunami.averageVelocityX = 0;
                object.__skunami.averageVelocityZ = 0;
            } else {
                object.__skunami.averageVelocityX = object.__skunami.totalVelocityX / object.__skunami.totalArea;
                object.__skunami.averageVelocityZ = object.__skunami.totalVelocityZ / object.__skunami.totalArea;
            }

            //calculate forces that should be exerted on this object
            object.__skunami.forceX = object.__skunami.averageVelocityX / dt * object.__skunami.mass;
            object.__skunami.forceY = object.__skunami.totalDisplacedVol * that.__density * that.__gravity;
            object.__skunami.forceZ = object.__skunami.averageVelocityZ / dt * object.__skunami.mass;

            //call exertForce callbacks
            if (that.__callbacks.hasOwnProperty('exertForce')) {
                var renderCallbacks = that.__callbacks['exertForce'];
                var i, len;
                for (i = 0, len = renderCallbacks.length; i < len; i++) {
                    renderCallbacks[i](object, new THREE.Vector3(object.__skunami.forceX, object.__skunami.forceY, object.__skunami.forceZ));
                }
            }

            // }

            //hide current mesh
            object.visible = false;
        }
    });

    //remove scene override material
    this.__scene.overrideMaterial = null;

    //restore visibility in the scene
    this.__scene.traverse(function (object) {
        object.visible = object.visibleStore;
    });

    //---------------------------------------------
    //calculate rigid bodies' influence on water:
    //---------------------------------------------

    //blur the obstacles map
    this.__rttQuadMesh.material = this.__gaussianBlurXMaterial;
    this.__gaussianBlurXMaterial.uniforms['uTexture'].value = this.__rttDynObstaclesRenderTarget;
    this.__gaussianBlurXMaterial.uniforms['uTexelSize'].value = 1.0 / this.__res;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttDynObstaclesBlurredRenderTarget, false);  //need to render to another target to avoid corrupting original accumulated
    this.__rttQuadMesh.material = this.__gaussianBlurYMaterial;
    this.__gaussianBlurYMaterial.uniforms['uTexture'].value = this.__rttDynObstaclesBlurredRenderTarget;
    this.__gaussianBlurYMaterial.uniforms['uTexelSize'].value = 1.0 / this.__res;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttDynObstaclesBlurredRenderTarget, false);

    //calculate a map with additional heights to disturb water, based on differences in water volumes between frames
    this.__rttQuadMesh.material = this.__calcDisturbMapMaterial;
    this.__calcDisturbMapMaterial.uniforms['uTexture'].value = this.__rttDynObstaclesBlurredRenderTarget;  //use blurred obstacle maps
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttDisturbMapRenderTarget, false);
    this.__disturbMapHasUpdated = true;

};
//Returns the pixel unsigned byte data for the render target texture (readPixels() can only return unsigned byte data)
SKUNAMI.GpuHeightFieldWater.prototype.__getPixelByteDataForRenderTarget = function (renderTarget, pixelByteData, width, height) {

    //I need to read in pixel data from WebGLRenderTarget but there seems to be no direct way.
    //Seems like I have to do some native WebGL stuff with readPixels().

    var gl = this.__renderer.getContext();

    //bind texture to gl context
    gl.bindFramebuffer(gl.FRAMEBUFFER, renderTarget.__webglFramebuffer);

    //attach texture
    // gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, renderTarget.__webglTexture, 0);

    //read pixels
    gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixelByteData);

    //unbind
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

};
SKUNAMI.GpuHeightFieldWater.prototype.__getPixelEncodedByteData = function (renderTarget, pixelByteData, channelId, width, height) {

    //encode the float data into an unsigned byte RGBA texture
    this.__rttQuadMesh.material = this.__rttEncodeFloatMaterial;
    this.__rttEncodeFloatMaterial.uniforms['uTexture'].value = renderTarget;
    this.__rttEncodeFloatMaterial.uniforms['uChannelMask'].value.copy(this.__channelVectors[channelId]);
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttFloatEncoderRenderTarget, false);

    this.__getPixelByteDataForRenderTarget(this.__rttFloatEncoderRenderTarget, pixelByteData, width, height);
};
/**
 * Returns the pixel float data for the water textures<br/><strong>NOTE: This is an expensive operation.</strong>
 * @return {Float32Array} Float data of the water texture
 */
SKUNAMI.GpuHeightFieldWater.prototype.getPixelFloatData = function () {

    //get the encoded byte data first
    this.__getPixelEncodedByteData(this.__rttRenderTarget1, this.__pixelByteData, 'r', this.__res, this.__res);

    //cast to float
    var pixelFloatData = new Float32Array(this.__pixelByteData.buffer);
    return pixelFloatData;
};
/**
 * Adds callback function that is executed at specific times
 * @param {string} type Type of callback: 'exertForce' (only choice available now)
 * @param {function} callbackFn Callback function
 */
SKUNAMI.GpuHeightFieldWater.prototype.addCallback = function (type, callbackFn) {
    if (!this.__callbacks.hasOwnProperty(type)) {
        this.__callbacks[type] = [];
    }
    if (callbackFn) {
        if (typeof callbackFn === 'function') {
            this.__callbacks[type].push(callbackFn);
        } else {
            throw new Error('Specified callbackFn is not a function');
        }
    } else {
        throw new Error('Callback function not defined');
    }
};
/**
 * Gets the water texture that is used for displacement of mesh
 * @return {THREE.WebGLRenderTarget} Water texture that is used for displacement of mesh
 */
SKUNAMI.GpuHeightFieldWater.prototype.getWaterDisplayTexture = function () {
    return this.__rttWaterDisplay;
};
/**
 * Gets the obstacle texture
 * @return {THREE.WebGLRenderTarget} Obstacles texture
 */
SKUNAMI.GpuHeightFieldWater.prototype.getObstaclesDisplayTexture = function () {
    return this.__rttObstaclesDisplay;
};

/**
 * Abstract base class for GPU height field surface water simulations
 * @constructor
 * @abstract
 * @extends {SKUNAMI.GpuHeightFieldWater}
 */
SKUNAMI.GpuHeightFieldSurfaceWater = function (options) {
    this.__meanHeight = options.meanHeight || 0;
    SKUNAMI.GpuHeightFieldWater.call(this, options);
};
//inherit
SKUNAMI.GpuHeightFieldSurfaceWater.prototype = Object.create(SKUNAMI.GpuHeightFieldWater.prototype);
SKUNAMI.GpuHeightFieldSurfaceWater.prototype.constructor = SKUNAMI.GpuHeightFieldSurfaceWater;
//override
/**
 * Floods the scene by the given volume
 * @param  {number} volume Volume of water to flood the scene with, in cubic scene units
 */
SKUNAMI.GpuHeightFieldSurfaceWater.prototype.flood = function (volume) {
    this.__meanHeight += volume / (this.__size * this.__size);
};
//methods
/**
 * Gets mean height
 * @returns {number} Mean height
 */
SKUNAMI.GpuHeightFieldSurfaceWater.prototype.getMeanHeight = function () {
    return this.__meanHeight;
};
/**
 * Sets mean height
 * @param {number} value Mean height
 */
SKUNAMI.GpuHeightFieldSurfaceWater.prototype.setMeanHeight = function (value) {
    this.__meanHeight = value;
};

/**
 * GPU height field water simulation based on "Fast Water Simulation for Games Using Height Fields" (Matthias Mueller-Fisher, GDC2008)
 * @constructor
 * @extends {SKUNAMI.GpuHeightFieldSurfaceWater}
 * @param {object} options Options
 * @param {THREE.WebGLRenderer} options.renderer Three.js WebGL renderer
 * @param {THREE.Scene} options.scene Three.js scene
 * @param {THREE.Mesh} options.mesh Three.js mesh for sculpting
 * @param {number} options.size Size of mesh
 * @param {number} options.res Resolution of mesh
 * @param {number} options.dampingFactor Damping factor for the sim
 * @param {number} options.horizontalSpeed Horizontal speed
 * @param {number} [options.multiSteps=1] Number of full steps to take per frame, to speed up some of algorithms that are slow to propagate at high mesh resolutions
 * @param {number} [options.meanHeight=0] Mean height of the water simulation
 * @param {number} [options.density=1000] Density of water (kg per cubic metres)
 */
SKUNAMI.GpuMuellerGdc2008Water = function (options) {

    if (typeof options.horizontalSpeed === 'undefined') {
        throw new Error('horizontalSpeed not specified');
    }
    this.__horizontalSpeed = options.horizontalSpeed;

    SKUNAMI.GpuHeightFieldSurfaceWater.call(this, options);

    this.__maxDt = this.__segmentSize / this.__horizontalSpeed;  //based on CFL condition
};
//inherit
SKUNAMI.GpuMuellerGdc2008Water.prototype = Object.create(SKUNAMI.GpuHeightFieldSurfaceWater.prototype);
SKUNAMI.GpuMuellerGdc2008Water.prototype.constructor = SKUNAMI.GpuMuellerGdc2008Water;
//override
SKUNAMI.GpuMuellerGdc2008Water.prototype.__getWaterFragmentShaderContent = function () {
    return this.__shaders.frag['hfWater_muellerGdc2008'];
};
SKUNAMI.GpuMuellerGdc2008Water.prototype.__setupShaders = function () {
    SKUNAMI.GpuHeightFieldSurfaceWater.prototype.__setupShaders.call(this);

    //add uHorizontalSpeed into the uniforms
    this.__waterSimMaterial.uniforms['uHorizontalSpeed'] = { type: 'f', value: this.__horizontalSpeed };
};
SKUNAMI.GpuMuellerGdc2008Water.prototype.__calculateSubsteps = function (dt) {
    return Math.ceil(1.5 * dt / this.__maxDt);  //not always stable without a multiplier (using 1.5 now)
};

/**
 * GPU height field water simulation based on HelloWorld code of "Fast Water Simulation for Games Using Height Fields" (Matthias Mueller-Fisher, GDC2008)
 * @constructor
 * @extends {SKUNAMI.GpuHeightFieldSurfaceWater}
 * @param {object} options Options
 * @param {THREE.WebGLRenderer} options.renderer Three.js WebGL renderer
 * @param {THREE.Scene} options.scene Three.js scene
 * @param {THREE.Mesh} options.mesh Three.js mesh for sculpting
 * @param {number} options.size Size of mesh
 * @param {number} options.res Resolution of mesh
 * @param {number} options.dampingFactor Damping factor for the sim
 * @param {number} [options.multiSteps=1] Number of full steps to take per frame, to speed up some of algorithms that are slow to propagate at high mesh resolutions
 * @param {number} [options.meanHeight=0] Mean height of the water simulation
 * @param {number} [options.density=1000] Density of water (kg per cubic metres)
 */
SKUNAMI.GpuMuellerGdc2008HwWater = function (options) {
    SKUNAMI.GpuHeightFieldSurfaceWater.call(this, options);
};
//inherit
SKUNAMI.GpuMuellerGdc2008HwWater.prototype = Object.create(SKUNAMI.GpuHeightFieldSurfaceWater.prototype);
SKUNAMI.GpuMuellerGdc2008HwWater.prototype.constructor = SKUNAMI.GpuMuellerGdc2008HwWater;
//override
SKUNAMI.GpuMuellerGdc2008HwWater.prototype.__getWaterFragmentShaderContent = function () {
    return this.__shaders.frag['hfWater_muellerGdc2008Hw'];
};

/**
 * GPU height field water simulation based on {@link http://freespace.virgin.net/hugo.elias/graphics/x_water.htm}
 * @constructor
 * @extends {SKUNAMI.GpuHeightFieldSurfaceWater}
 * @param {object} options Options
 * @param {THREE.WebGLRenderer} options.renderer Three.js WebGL renderer
 * @param {THREE.Scene} options.scene Three.js scene
 * @param {THREE.Mesh} options.mesh Three.js mesh for sculpting
 * @param {number} options.size Size of mesh
 * @param {number} options.res Resolution of mesh
 * @param {number} options.dampingFactor Damping factor for the sim
 * @param {number} [options.multiSteps=1] Number of full steps to take per frame, to speed up some of algorithms that are slow to propagate at high mesh resolutions
 * @param {number} [options.meanHeight=0] Mean height of the water simulation
 * @param {number} [options.density=1000] Density of water (kg per cubic metres)
 */
SKUNAMI.GpuXWater = function (options) {
    SKUNAMI.GpuHeightFieldSurfaceWater.call(this, options);
};
//inherit
SKUNAMI.GpuXWater.prototype = Object.create(SKUNAMI.GpuHeightFieldSurfaceWater.prototype);
SKUNAMI.GpuXWater.prototype.constructor = SKUNAMI.GpuXWater;
//override
SKUNAMI.GpuXWater.prototype.__getWaterFragmentShaderContent = function () {
    return this.__shaders.frag['hfWater_xWater'];
};

/**
 * GPU height field water simulation based on "Interactive Water Surfaces" (Jerry Tessendorf, Game Programming Gems 4)
 * @constructor
 * @extends {SKUNAMI.GpuHeightFieldSurfaceWater}
 * @param {object} options Options
 * @param {THREE.WebGLRenderer} options.renderer Three.js WebGL renderer
 * @param {THREE.Scene} options.scene Three.js scene
 * @param {THREE.Mesh} options.mesh Three.js mesh for sculpting
 * @param {number} options.size Size of mesh
 * @param {number} options.res Resolution of mesh
 * @param {number} options.dampingFactor Damping factor for the sim
 * @param {number} [options.multiSteps=1] Number of full steps to take per frame, to speed up some of algorithms that are slow to propagate at high mesh resolutions
 * @param {number} [options.meanHeight=0] Mean height of the water simulation
 * @param {number} [options.density=1000] Density of water (kg per cubic metres)
 */
SKUNAMI.GpuTessendorfIWaveWater = function (options) {

    //not giving user the choice of kernel size.
    //wanted to use 6 as recommended, but that doesn't work well with mesh res of 256 (ripples look like they go inwards rather than outwards).
    //radius of 2 seems to work ok for mesh 256.
    this.__kernelRadius = 2;

    SKUNAMI.GpuHeightFieldSurfaceWater.call(this, options);

    this.__loadKernelTexture();
};
//inherit
SKUNAMI.GpuTessendorfIWaveWater.prototype = Object.create(SKUNAMI.GpuHeightFieldSurfaceWater.prototype);
SKUNAMI.GpuTessendorfIWaveWater.prototype.constructor = SKUNAMI.GpuTessendorfIWaveWater;
//override
SKUNAMI.GpuTessendorfIWaveWater.prototype.__getWaterFragmentShaderContent = function () {
    return this.__shaders.frag['hfWater_tessendorfIWave'];
};
SKUNAMI.GpuTessendorfIWaveWater.prototype.__setupShaders = function () {

    SKUNAMI.GpuHeightFieldSurfaceWater.prototype.__setupShaders.call(this);

    this.__convolveMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uWaterTexture: { type: 't', value: this.__emptyTexture },
            uTexelSize: { type: 'v2', value: new THREE.Vector2(this.__texelSize, this.__texelSize) },
            uKernel: { type: "fv1", value: this.__kernelData }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['hfWater_tessendorfIWave_convolve']
    });

    this.__waterSimMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uWaterTexture: { type: 't', value: this.__emptyTexture },
            uTwoMinusDampTimesDt: { type: 'f', value: 0.0 },
            uOnePlusDampTimesDt: { type: 'f', value: 0.0 },
            uGravityTimesDtTimesDt: { type: 'f', value: 0.0 },
            uMeanHeight: { type: 'f', value: this.__meanHeight }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__getWaterFragmentShaderContent()
    });
};
SKUNAMI.GpuTessendorfIWaveWater.prototype.__waterSimPass = function (substepDt) {

    //convolve
    this.__rttQuadMesh.material = this.__convolveMaterial;
    this.__convolveMaterial.uniforms['uWaterTexture'].value = this.__rttRenderTarget2;
    this.__convolveMaterial.uniforms['uKernel'].value = this.__kernelData;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTarget1, false);
    this.__swapRenderTargets();

    //water sim
    this.__rttQuadMesh.material = this.__waterSimMaterial;
    this.__waterSimMaterial.uniforms['uWaterTexture'].value = this.__rttRenderTarget2;
    this.__waterSimMaterial.uniforms['uTwoMinusDampTimesDt'].value = 2.0 - this.__dampingFactor * substepDt;
    this.__waterSimMaterial.uniforms['uOnePlusDampTimesDt'].value = 1.0 + this.__dampingFactor * substepDt;
    this.__waterSimMaterial.uniforms['uGravityTimesDtTimesDt'].value = -this.__gravity * substepDt * substepDt;
    this.__waterSimMaterial.uniforms['uMeanHeight'].value = this.__meanHeight;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTarget1, false);
    this.__swapRenderTargets();

};
//data
SKUNAMI.GpuTessendorfIWaveWater.prototype.__kernels = {

    2: {
        "0": {
            "0": 1.0,
            "1": 0.67827660633313791,
            "2": 0.15642080318487095,
            "-1": 0.67827660633313791,
            "-2": 0.15642080318487095
        },
        "1": {
            "0": 0.67827660633313791,
            "1": 0.44456489541854272,
            "2": 0.072257536168982492,
            "-1": 0.44456489541854272,
            "-2": 0.072257536168982492
        },
        "2": {
            "0": 0.15642080318487095,
            "1": 0.072257536168982492,
            "2": -0.049938776894223477,
            "-1": 0.072257536168982492,
            "-2": -0.049938776894223477
        },
        "-1": {
            "0": 0.67827660633313791,
            "1": 0.44456489541854272,
            "2": 0.072257536168982492,
            "-1": 0.44456489541854272,
            "-2": 0.072257536168982492
        },
        "-2": {
            "0": 0.15642080318487095,
            "1": 0.072257536168982492,
            "2": -0.049938776894223477,
            "-1": 0.072257536168982492,
            "-2": -0.049938776894223477
        }
    },

    6: {
        "0": {
            "0": 1.0,
            "1": 0.67827660633313791,
            "2": 0.15642080318487095,
            "3": -0.065327390570961569,
            "4": -0.06444781066526209,
            "5": -0.031433210050842826,
            "6": -0.014956820349629208,
            "-2": 0.15642080318487095,
            "-6": -0.014956820349629208,
            "-5": -0.031433210050842826,
            "-4": -0.06444781066526209,
            "-3": -0.065327390570961569,
            "-1": 0.67827660633313791
        },
        "1": {
            "0": 0.67827660633313791,
            "1": 0.44456489541854272,
            "2": 0.072257536168982492,
            "3": -0.073774850743644649,
            "4": -0.059964799734097914,
            "5": -0.029072820004281557,
            "6": -0.014159108191184992,
            "-2": 0.072257536168982492,
            "-6": -0.014159108191184992,
            "-5": -0.029072820004281557,
            "-4": -0.059964799734097914,
            "-3": -0.073774850743644649,
            "-1": 0.44456489541854272
        },
        "2": {
            "0": 0.15642080318487095,
            "1": 0.072257536168982492,
            "2": -0.049938776894223477,
            "3": -0.075937865123835754,
            "4": -0.047262518792478211,
            "5": -0.023276902009199931,
            "6": -0.012144644038531688,
            "-2": -0.049938776894223477,
            "-6": -0.012144644038531688,
            "-5": -0.023276902009199931,
            "-4": -0.047262518792478211,
            "-3": -0.075937865123835754,
            "-1": 0.072257536168982492
        },
        "3": {
            "0": -0.065327390570961569,
            "1": -0.073774850743644649,
            "2": -0.075937865123835754,
            "3": -0.055537014106713647,
            "4": -0.031433210050842826,
            "5": -0.016789615952969669,
            "6": -0.0097042154845355822,
            "-2": -0.075937865123835754,
            "-6": -0.0097042154845355822,
            "-5": -0.016789615952969669,
            "-4": -0.031433210050842826,
            "-3": -0.055537014106713647,
            "-1": -0.073774850743644649
        },
        "4": {
            "0": -0.06444781066526209,
            "1": -0.059964799734097914,
            "2": -0.047262518792478211,
            "3": -0.031433210050842826,
            "4": -0.019006732548424117,
            "5": -0.011577903734318462,
            "6": -0.007455793802651235,
            "-2": -0.047262518792478211,
            "-6": -0.007455793802651235,
            "-5": -0.011577903734318462,
            "-4": -0.019006732548424117,
            "-3": -0.031433210050842826,
            "-1": -0.059964799734097914
        },
        "5": {
            "0": -0.031433210050842826,
            "1": -0.029072820004281557,
            "2": -0.023276902009199931,
            "3": -0.016789615952969669,
            "4": -0.011577903734318462,
            "5": -0.0079990693931909686,
            "6": -0.0056408890389552952,
            "-2": -0.023276902009199931,
            "-6": -0.0056408890389552952,
            "-5": -0.0079990693931909686,
            "-4": -0.011577903734318462,
            "-3": -0.016789615952969669,
            "-1": -0.029072820004281557
        },
        "6": {
            "0": -0.014956820349629208,
            "1": -0.014159108191184992,
            "2": -0.012144644038531688,
            "3": -0.0097042154845355822,
            "4": -0.007455793802651235,
            "5": -0.0056408890389552952,
            "6": -0.0042622070432495182,
            "-2": -0.012144644038531688,
            "-6": -0.0042622070432495182,
            "-5": -0.0056408890389552952,
            "-4": -0.007455793802651235,
            "-3": -0.0097042154845355822,
            "-1": -0.014159108191184992
        },
        "-2": {
            "0": 0.15642080318487095,
            "1": 0.072257536168982492,
            "2": -0.049938776894223477,
            "3": -0.075937865123835754,
            "4": -0.047262518792478211,
            "5": -0.023276902009199931,
            "6": -0.012144644038531688,
            "-2": -0.049938776894223477,
            "-6": -0.012144644038531688,
            "-5": -0.023276902009199931,
            "-4": -0.047262518792478211,
            "-3": -0.075937865123835754,
            "-1": 0.072257536168982492
        },
        "-6": {
            "0": -0.014956820349629208,
            "1": -0.014159108191184992,
            "2": -0.012144644038531688,
            "3": -0.0097042154845355822,
            "4": -0.007455793802651235,
            "5": -0.0056408890389552952,
            "6": -0.0042622070432495182,
            "-2": -0.012144644038531688,
            "-6": -0.0042622070432495182,
            "-5": -0.0056408890389552952,
            "-4": -0.007455793802651235,
            "-3": -0.0097042154845355822,
            "-1": -0.014159108191184992
        },
        "-5": {
            "0": -0.031433210050842826,
            "1": -0.029072820004281557,
            "2": -0.023276902009199931,
            "3": -0.016789615952969669,
            "4": -0.011577903734318462,
            "5": -0.0079990693931909686,
            "6": -0.0056408890389552952,
            "-2": -0.023276902009199931,
            "-6": -0.0056408890389552952,
            "-5": -0.0079990693931909686,
            "-4": -0.011577903734318462,
            "-3": -0.016789615952969669,
            "-1": -0.029072820004281557
        },
        "-4": {
            "0": -0.06444781066526209,
            "1": -0.059964799734097914,
            "2": -0.047262518792478211,
            "3": -0.031433210050842826,
            "4": -0.019006732548424117,
            "5": -0.011577903734318462,
            "6": -0.007455793802651235,
            "-2": -0.047262518792478211,
            "-6": -0.007455793802651235,
            "-5": -0.011577903734318462,
            "-4": -0.019006732548424117,
            "-3": -0.031433210050842826,
            "-1": -0.059964799734097914
        },
        "-3": {
            "0": -0.065327390570961569,
            "1": -0.073774850743644649,
            "2": -0.075937865123835754,
            "3": -0.055537014106713647,
            "4": -0.031433210050842826,
            "5": -0.016789615952969669,
            "6": -0.0097042154845355822,
            "-2": -0.075937865123835754,
            "-6": -0.0097042154845355822,
            "-5": -0.016789615952969669,
            "-4": -0.031433210050842826,
            "-3": -0.055537014106713647,
            "-1": -0.073774850743644649
        },
        "-1": {
            "0": 0.67827660633313791,
            "1": 0.44456489541854272,
            "2": 0.072257536168982492,
            "3": -0.073774850743644649,
            "4": -0.059964799734097914,
            "5": -0.029072820004281557,
            "6": -0.014159108191184992,
            "-2": 0.072257536168982492,
            "-6": -0.014159108191184992,
            "-5": -0.029072820004281557,
            "-4": -0.059964799734097914,
            "-3": -0.073774850743644649,
            "-1": 0.44456489541854272
        }
    }

};
//methods
SKUNAMI.GpuTessendorfIWaveWater.prototype.__loadKernelTexture = function () {

    //load this.__G from json file
    // var url = '../kernels/iWave_kernels_' + this.__kernelRadius + '.json';
    // var that = this;
    // $.ajax({
        // url: url,
        // async: false
    // }).done(function (data) {
        // that.__G = data;
    // }).error(function (xhr, textStatus, error) {
        // throw new Error('error loading ' + url + ': ' + error);
    // });

    this.__G = this.__kernels[this.__kernelRadius];
    if (typeof this.__G === 'undefined') {
        throw new Error('Unable to load iWave kernel with radius: ' + this.__kernelRadius);
    }

    //create a data texture from G
    var twoTimesKernelPlusOne = 2 * this.__kernelRadius + 1;
    this.__kernelData = new Float32Array(twoTimesKernelPlusOne * twoTimesKernelPlusOne);
    var idxX, idxY, idx, value, y;
    for (idxY in this.__G) {
        if (this.__G.hasOwnProperty(idxY)) {
            y = this.__G[idxY];
            for (idxX in y) {
                if (y.hasOwnProperty(idxX)) {
                    value = y[idxX];
                    idx = (parseInt(idxY, 10) + this.__kernelRadius) * twoTimesKernelPlusOne + (parseInt(idxX, 10) + this.__kernelRadius);
                    this.__kernelData[idx] = value;
                }
            }
        }
    }

};

/**
 * GPU height field water based on the hydrostatic pipe model ("Fast Hydraulic Erosion Simulation and Visualization on GPU", Xing Mei, Philippe Decaudin and Bao-Gang Hu, Pacific Graphics 2007)
 * @constructor
 * @extends {SKUNAMI.GpuHeightFieldWater}
 * @param {object} options Options
 * @param {THREE.WebGLRenderer} options.renderer Three.js WebGL renderer
 * @param {THREE.Scene} options.scene Three.js scene
 * @param {THREE.Mesh} options.mesh Three.js mesh for sculpting
 * @param {number} options.size Size of mesh
 * @param {number} options.res Resolution of mesh
 * @param {number} options.dampingFactor Damping factor for the sim
 * @param {number} [options.multiSteps=1] Number of full steps to take per frame, to speed up some of algorithms that are slow to propagate at high mesh resolutions
 * @param {THREE.WebGLRenderTarget} [options.terrainTexture=null] Terrain texture that creates a base height which affects the water
 * @param {number} [options.initialWaterHeight=0] Initial height of water that is above the terrain
 * @param {number} [options.density=1000] Density of water (kg per cubic metres)
 */
SKUNAMI.GpuPipeModelWater = function (options) {

    this.__minWaterHeight = -0.05;
    this.__initialWaterHeight = options.initialWaterHeight || 0.0;
    this.__initialWaterHeight += this.__minWaterHeight;

    SKUNAMI.GpuHeightFieldWater.call(this, options);

    this.__terrainTexture = options.terrainTexture || this.__emptyTexture;

    this.__isSourcing = false;
    this.__sourceUvPos = new THREE.Vector2();
    this.__sourceAmount = 0;
    this.__sourceRadius = 0.0025 * this.__size;

    //some constants
    this.__atmosPressure = 0;  //assume one constant atmos pressure throughout
    this.__pipeLength = this.__segmentSize;
    this.__pipeCrossSectionArea = this.__pipeLength * this.__pipeLength;  //square cross-section area
    this.__pipeCrossSectionArea *= this.__res / 10;  //scale according to resolution
    this.__heightToFluxFactorNoDt = this.__pipeCrossSectionArea * this.__gravity / this.__pipeLength;

    this.__maxHorizontalSpeed = 10.0;  //just an arbitrary upper-bound estimate //TODO: link this to cross-section area
    this.__maxDt = this.__segmentSize / this.__maxHorizontalSpeed;  //based on CFL condition

};
//inherit
SKUNAMI.GpuPipeModelWater.prototype = Object.create(SKUNAMI.GpuHeightFieldWater.prototype);
SKUNAMI.GpuPipeModelWater.prototype.constructor = SKUNAMI.GpuPipeModelWater;
//override
SKUNAMI.GpuPipeModelWater.prototype.__getWaterFragmentShaderContent = function () {
    return this.__shaders.frag['hfWater_pipeModel'];
};
SKUNAMI.GpuPipeModelWater.prototype.__setupShaders = function () {

    SKUNAMI.GpuHeightFieldWater.prototype.__setupShaders.call(this);

    this.__waterSimMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTerrainTexture: { type: 't', value: this.__emptyTexture },
            uWaterTexture: { type: 't', value: this.__emptyTexture },
            uFluxTexture: { type: 't', value: this.__emptyTexture },
            uStaticObstaclesTexture: { type: 't', value: this.__emptyTexture },
            uBoundaryTexture: { type: 't', value: this.__emptyTexture },
            uTexelSize: { type: 'v2', value: new THREE.Vector2(this.__texelSize, this.__texelSize) },
            uDampingFactor: { type: 'f', value: this.__dampingFactor },
            uHeightToFluxFactor: { type: 'f', value: 0.0 },
            uSegmentSizeSquared: { type: 'f', value: this.__segmentSizeSquared },
            uDt: { type: 'f', value: 0.0 },
            uMinWaterHeight: { type: 'f', value: this.__minWaterHeight }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['hfWater_pipeModel_calcFlux']
    });

    this.__waterSimMaterial2 = new THREE.ShaderMaterial({
        uniforms: {
            uWaterTexture: { type: 't', value: this.__emptyTexture },
            uFluxTexture: { type: 't', value: this.__emptyTexture },
            uTexelSize: { type: 'v2', value: new THREE.Vector2(this.__texelSize, this.__texelSize) },
            uSegmentSize: { type: 'f', value: this.__segmentSize },
            uDt: { type: 'f', value: 0.0 },
            uMinWaterHeight: { type: 'f', value: this.__minWaterHeight }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__getWaterFragmentShaderContent()
    });

    this.__calcFinalWaterHeightMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTerrainTexture: { type: 't', value: this.__emptyTexture },
            uStaticObstaclesTexture: { type: 't', value: this.__emptyTexture },
            uWaterTexture: { type: 't', value: this.__emptyTexture },
            uMultiplyTexture: { type: 't', value: this.__emptyTexture },
            uMaskOffset: { type: 'f', value: this.__minWaterHeight }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['hfWater_pipeModel_calcFinalWaterHeight']
    });

    //add flood uniforms into disturb material
    this.__disturbAndSourceMaterial.uniforms['uUseObstacleTexture'].value = false;
    this.__disturbAndSourceMaterial.uniforms['uIsFlooding'] = { type: 'i', value: 0 };
    this.__disturbAndSourceMaterial.uniforms['uFloodAmount'] = { type: 'f', value: this.__floodAmount };
};
SKUNAMI.GpuPipeModelWater.prototype.__setupRttRenderTargets = function () {

    SKUNAMI.GpuHeightFieldWater.prototype.__setupRttRenderTargets.call(this);

    //create RTT render targets for flux (we need two to do feedback)
    if (this.__supportsTextureFloatLinear) {
        this.__rttRenderTargetFlux1 = new THREE.WebGLRenderTarget(this.__res, this.__res, this.__linearFloatRgbaParams);
    } else {
        this.__rttRenderTargetFlux1 = new THREE.WebGLRenderTarget(this.__res, this.__res, this.__nearestFloatRgbaParams);
    }
    this.__rttRenderTargetFlux1.generateMipmaps = false;
    this.__clearRenderTarget(this.__rttRenderTargetFlux1, 0.0, 0.0, 0.0, 0.0);  //clear render target (necessary for FireFox)
    this.__rttRenderTargetFlux2 = this.__rttRenderTargetFlux1.clone();
    this.__clearRenderTarget(this.__rttRenderTargetFlux2, 0.0, 0.0, 0.0, 0.0);  //clear render target (necessary for FireFox)

    //create another RTT render target for storing the combined terrain + water heights
    this.__rttCombinedHeight = this.__rttRenderTarget1.clone();
    this.__clearRenderTarget(this.__rttCombinedHeight, 0.0, 0.0, 0.0, 0.0);  //clear render target (necessary for FireFox)
};
/**
 * Sources water into the simulation and causes water level to rise.
 * This is different from {@linkcode SKUNAMI.GpuHeightFieldWater#disturb disturb} which is meant to only create small ripples on the water surface.
 * A negative amount will create a sink which removes water from the simulation and causes water level to fall.
 * @param  {THREE.Vector3} position World-space position to source at
 * @param  {number} amount Amount of water to source. A negative amount removes water from the system.
 * @param  {number} radius Radius of water to source
 */
SKUNAMI.GpuPipeModelWater.prototype.source = function (position, amount, radius) {
    this.__isSourcing = true;
    this.__sourceUvPos.x = (position.x + this.__halfSize) / this.__size;
    this.__sourceUvPos.y = (position.z + this.__halfSize) / this.__size;
    this.__sourceAmount = amount;
    this.__sourceRadius = radius;
};
/**
 * Floods the scene by the given volume
 * @param  {number} volume Volume of water to flood the scene with, in cubic scene units
 */
SKUNAMI.GpuPipeModelWater.prototype.flood = function (volume) {
    this.__isFlooding = true;
    this.__floodAmount = volume / (this.__size * this.__size);
};
SKUNAMI.GpuPipeModelWater.prototype.__disturbPass = function () {
    var shouldRender = false;
    if (this.__disturbMapHasUpdated) {
        // this.__disturbAndSourceMaterial.uniforms['uStaticObstaclesTexture'].value = this.__rttDynObstaclesRenderTarget;
        this.__disturbAndSourceMaterial.uniforms['uDisturbTexture'].value = this.__rttDisturbMapRenderTarget;
        shouldRender = true;
    }
    if (this.__isDisturbing && this.__disturbAmount !== 0.0) {
        this.__disturbAndSourceMaterial.uniforms['uIsDisturbing'].value = this.__isDisturbing;
        this.__disturbAndSourceMaterial.uniforms['uDisturbPos'].value.copy(this.__disturbUvPos);
        this.__disturbAndSourceMaterial.uniforms['uDisturbAmount'].value = this.__disturbAmount;
        this.__disturbAndSourceMaterial.uniforms['uDisturbRadius'].value = this.__disturbRadius / this.__size;
        shouldRender = true;
    }
    if (this.__isSourcing && this.__sourceAmount !== 0.0) {
        this.__disturbAndSourceMaterial.uniforms['uIsSourcing'].value = this.__isSourcing;
        this.__disturbAndSourceMaterial.uniforms['uSourcePos'].value.copy(this.__sourceUvPos);
        this.__disturbAndSourceMaterial.uniforms['uSourceAmount'].value = this.__sourceAmount;
        this.__disturbAndSourceMaterial.uniforms['uSourceRadius'].value = this.__sourceRadius / this.__size;
        shouldRender = true;
    }
    if (this.__isFlooding && this.__floodAmount !== 0.0) {
        this.__disturbAndSourceMaterial.uniforms['uIsFlooding'].value = this.__isFlooding;
        this.__disturbAndSourceMaterial.uniforms['uFloodAmount'].value = this.__floodAmount;
        shouldRender = true;
    }
    if (shouldRender) {
        this.__rttQuadMesh.material = this.__disturbAndSourceMaterial;
        this.__disturbAndSourceMaterial.uniforms['uTexture'].value = this.__rttRenderTarget2;
        this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTarget1, false);
        this.__swapRenderTargets();

        this.__disturbMapHasUpdated = false;
        this.__disturbAndSourceMaterial.uniforms['uDisturbTexture'].value = this.__emptyTexture;
        this.__isDisturbing = false;
        this.__disturbAndSourceMaterial.uniforms['uIsDisturbing'].value = false;
        this.__isSourcing = false;
        this.__disturbAndSourceMaterial.uniforms['uIsSourcing'].value = false;
        this.__isFlooding = false;
        this.__disturbAndSourceMaterial.uniforms['uIsFlooding'].value = false;
    }
};
SKUNAMI.GpuPipeModelWater.prototype.__calculateSubsteps = function (dt) {
    return Math.ceil(5.0 * dt / this.__maxDt);  //not always stable without a multiplier
};
SKUNAMI.GpuPipeModelWater.prototype.__resetPass = function () {
    //init rttRenderTarget2 to initial height value
    this.__clearRenderTarget(this.__rttRenderTarget2, this.__initialWaterHeight, this.__initialWaterHeight, this.__initialWaterHeight, this.__initialWaterHeight);

    //init all channels of flux texture to 0.0
    this.__clearRenderTarget(this.__rttRenderTargetFlux2, 0.0, 0.0, 0.0, 0.0);
};
SKUNAMI.GpuPipeModelWater.prototype.__waterSimPass = function (substepDt) {

    //calculate flux
    this.__rttQuadMesh.material = this.__waterSimMaterial;
    this.__waterSimMaterial.uniforms['uTerrainTexture'].value = this.__terrainTexture;
    this.__waterSimMaterial.uniforms['uWaterTexture'].value = this.__rttRenderTarget2;
    this.__waterSimMaterial.uniforms['uFluxTexture'].value = this.__rttRenderTargetFlux2;
    this.__waterSimMaterial.uniforms['uStaticObstaclesTexture'].value = this.__rttStaticObstaclesRenderTarget;
    this.__waterSimMaterial.uniforms['uBoundaryTexture'].value = this.__boundaryTexture;
    this.__waterSimMaterial.uniforms['uHeightToFluxFactor'].value = this.__heightToFluxFactorNoDt * substepDt;
    this.__waterSimMaterial.uniforms['uDt'].value = substepDt;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTargetFlux1, false);
    this.__swapFluxRenderTargets();

    //water sim
    this.__rttQuadMesh.material = this.__waterSimMaterial2;
    this.__waterSimMaterial2.uniforms['uWaterTexture'].value = this.__rttRenderTarget2;
    this.__waterSimMaterial2.uniforms['uFluxTexture'].value = this.__rttRenderTargetFlux2;
    this.__waterSimMaterial2.uniforms['uDt'].value = substepDt;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTarget1, false);
    this.__swapRenderTargets();

};
SKUNAMI.GpuPipeModelWater.prototype.__postStepPass = function () {

    //combine terrain, static obstacle and water heights
    this.__rttQuadMesh.material = this.__calcFinalWaterHeightMaterial;
    this.__calcFinalWaterHeightMaterial.uniforms['uTerrainTexture'].value = this.__terrainTexture;
    this.__calcFinalWaterHeightMaterial.uniforms['uStaticObstaclesTexture'].value = this.__rttStaticObstaclesRenderTarget;
    this.__calcFinalWaterHeightMaterial.uniforms['uWaterTexture'].value = this.__rttRenderTarget2;
    this.__calcFinalWaterHeightMaterial.uniforms['uMultiplyTexture'].value = this.__boundaryTexture;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttCombinedHeight, false);

    //rebind render target to water mesh to ensure vertex shader gets the right texture
    this.__mesh.material.uniforms['uTexture'].value = this.__rttCombinedHeight;
};
SKUNAMI.GpuPipeModelWater.prototype.__swapFluxRenderTargets = function () {
    var temp = this.__rttRenderTargetFlux1;
    this.__rttRenderTargetFlux1 = this.__rttRenderTargetFlux2;
    this.__rttRenderTargetFlux2 = temp;
    // this.__rttQuadMesh.material.uniforms['uTexture'].value = this.__rttRenderTargetFlux2;
};