Source: skulpt.js

/**
 * @fileOverview A JavaScript/GLSL sculpting script for sculpting Three.js meshes
 * @author Skeel Lee <skeel@skeelogy.com>
 * @version 1.0.2
 *
 * @example
 * //How to setup a GPU Skulpt:
 *
 * //create a plane for sculpting
 * var TERRAIN_SIZE = 10;
 * var TERRAIN_RES = 256;
 * var terrainGeom = new THREE.PlaneGeometry(TERRAIN_SIZE, TERRAIN_SIZE, TERRAIN_RES - 1, TERRAIN_RES - 1);
 * terrainGeom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2));
 * var terrainMesh = new THREE.Mesh(terrainGeom, null);  //a custom material will be assigned later when using SKULPT.GpuSkulpt
 * scene.add(terrainMesh);
 *
 * //create a GpuSkulpt instance
 * var gpuSkulpt = new SKULPT.GpuSkulpt({
 *     renderer: renderer,
 *     mesh: terrainMesh,
 *     size: TERRAIN_SIZE,
 *     res: TERRAIN_RES
 * });
 *
 * //update every frame
 * renderer.clear();
 * gpuSkulpt.update(dt);  //have to do this after clear but before render
 * renderer.render(scene, camera);
 *
 * @example
 * //How to sculpt:
 *
 * //get sculpt position and show/hide cursor
 * var sculptPosition = getSculptPosition();  //do ray-intersection tests, for example, to determine where the user is clicking on the plane
 * if (sculptPosition) {
 *     gpuSkulpt.updateCursor(sculptPosition);
 *     gpuSkulpt.showCursor();
 * } else {
 *     gpuSkulpt.hideCursor();
 * }
 *
 * //sculpt
 * var sculptType = SKULPT.ADD;
 * var sculptAmount = 1.0;
 * gpuSkulpt.sculpt(sculptType, sculptPosition, sculptAmount);
 *
 * @example
 * //How to clear sculpts:
 *
 * //clear sculpts
 * gpuSkulpt.clear();
 *
 * @example
 * //How to change sculpt brush parameters:
 *
 * //change brush size
 * var brushSize = 1.0;
 * gpuSkulpt.setBrushSize(brushSize);
 *
 * //change brush amount
 * var brushAmount = 1.0;
 * gpuSkulpt.setBrushAmount(brushAmount);
 *
 * @example
 * //How to load sculpt data from an img:
 *
 * //get image data from canvas
 * var canvas = document.createElement('canvas');
 * var context = canvas.getContext('2d');
 * var img = document.getElementById('yourImageId');
 * context.drawImage(img, 0, 0, TERRAIN_RES, TERRAIN_RES);
 * var terrainImageData = context.getImageData(0, 0, TERRAIN_RES, TERRAIN_RES).data;
 *
 * //load sculpt using image data
 * var height = 1.0;
 * var midGreyIsLowest = false;
 * gpuSkulpt.loadFromImageData(terrainImageData, height, midGreyIsLowest);
 */

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

/**
 * Creates a GpuSkulpt instance for sculpting
 * @constructor
 * @param {object} options Options
 * @param {THREE.WebGLRenderer} options.renderer Three.js WebGL renderer
 * @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.proxyRes] resolution of proxy mesh
 */
SKULPT.GpuSkulpt = 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;
    this.__halfSize = this.__size / 2.0;
    if (typeof options.res === 'undefined') {
        throw new Error('res not specified');
    }
    this.__res = options.res;
    this.__proxyRes = options.proxyRes || this.__res;

    this.__actualToProxyRatio = this.__res / this.__proxyRes;
    this.__gridSize = this.__size / this.__res;
    this.__texelSize = 1.0 / this.__res;

    this.__imageProcessedData = new Float32Array(4 * this.__res * this.__res);

    this.__isSculpting = false;
    this.__sculptUvPos = new THREE.Vector2();

    this.__cursorHoverColor = new THREE.Vector3(0.4, 0.4, 0.4);
    this.__cursorAddColor = new THREE.Vector3(0.3, 0.5, 0.1);
    this.__cursorRemoveColor = new THREE.Vector3(0.5, 0.2, 0.1);

    this.__shouldClear = false;

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

    this.__nearestFloatRgbParams = {
        minFilter: THREE.NearestFilter,
        magFilter: THREE.NearestFilter,
        wrapS: THREE.ClampToEdgeWrapping,
        wrapT: THREE.ClampToEdgeWrapping,
        format: THREE.RGBFormat,
        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
    };

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

    this.__callbacks = {};

    this.__init();
};
SKULPT.GpuSkulpt.prototype.__shaders = {

    vert: {

        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: {

        skulpt: [

            //Fragment shader for sculpting

            "uniform sampler2D uBaseTexture;",
            "uniform sampler2D uSculptTexture1;",
            "uniform vec2 uTexelSize;",
            "uniform int uIsSculpting;",
            "uniform int uSculptType;",
            "uniform float uSculptAmount;",
            "uniform float uSculptRadius;",
            "uniform vec2 uSculptPos;",

            "varying vec2 vUv;",

            "float add(vec2 uv) {",
                "float len = length(uv - vec2(uSculptPos.x, 1.0 - uSculptPos.y));",
                "return uSculptAmount * smoothstep(uSculptRadius, 0.0, len);",
            "}",

            "void main() {",

                //r channel: height

                //read base texture
                "vec4 tBase = texture2D(uBaseTexture, vUv);",

                //read texture from previous step
                "vec4 t1 = texture2D(uSculptTexture1, vUv);",

                //add sculpt
                "if (uIsSculpting == 1) {",
                    "if (uSculptType == 1) {",  //add
                        "t1.r += add(vUv);",
                    "} else if (uSculptType == 2) {",  //remove
                        "t1.r -= add(vUv);",
                        "t1.r = max(0.0, tBase.r + t1.r) - tBase.r;",
                    "}",
                "}",

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

        ].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'),

        setColor: [

            //Fragment shader to set colors on a render target

            "uniform vec4 uColor;",

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

        ].join('\n'),

        scaleAndFlipV: [

            //Fragment shader to scale and flip a texture

            "uniform sampler2D uTexture;",
            "uniform float uScale;",

            "varying vec2 vUv;",

            "void main() {",
                "vec2 scaledAndFlippedUv = vec2(vUv.x * uScale, 1.0 - (vUv.y * uScale));",
                "gl_FragColor = texture2D(uTexture, scaledAndFlippedUv);",
            "}"

        ].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);",
                "}",

                "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'),

        lambertCursor: [

            //Fragment shader that does basic lambert shading.
            //This is the version that overlays a circular cursor patch.

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

            "uniform int uShowCursor;",
            "uniform vec2 uCursorPos;",
            "uniform float uCursorRadius;",
            "uniform vec3 uCursorColor;",

            "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",

                //combine components to get final color
                "vec3 finalColor = uBaseColor * (ambient + diffuse);",

                //mix in cursor color
                "if (uShowCursor == 1) {",
                    "float len = length(vUv - vec2(uCursorPos.x, 1.0 - uCursorPos.y));",
                    "finalColor = mix(finalColor, uCursorColor, smoothstep(uCursorRadius, 0.0, len));",
                "}",

                "gl_FragColor = vec4(finalColor, 1.0);",

                THREE.ShaderChunk['shadowmap_fragment'],

            "}"

        ].join('\n')

    }

};
/**
 * Gets the color of the cursor in hover mode
 * @return {THREE.Vector3} A vector that represents the color of the cursor in hover mode
 */
SKULPT.GpuSkulpt.prototype.getCursorHoverColor = function (r, g, b) {
    return this.__cursorHoverColor;
};
/**
 * Sets the color of the cursor in hover mode
 * @param {number} r Red floating-point value between 0 and 1, inclusive
 * @param {number} g Green floating-point value between 0 and 1, inclusive
 * @param {number} b Blue floating-point value between 0 and 1, inclusive
 */
SKULPT.GpuSkulpt.prototype.setCursorHoverColor = function (r, g, b) {
    this.__cursorHoverColor.copy(r, g, b);
};
/**
 * Gets the color of the cursor in add mode
 * @return {THREE.Vector3} A vector that represents the color of the cursor in add mode
 */
SKULPT.GpuSkulpt.prototype.getCursorAddColor = function (r, g, b) {
    return this.__cursorAddColor;
};
/**
 * Sets the color of the cursor in add mode
 * @param {number} r Red floating-point value between 0 and 1, inclusive
 * @param {number} g Green floating-point value between 0 and 1, inclusive
 * @param {number} b Blue floating-point value between 0 and 1, inclusive
 */
SKULPT.GpuSkulpt.prototype.setCursorAddColor = function (r, g, b) {
    this.__cursorAddColor.copy(r, g, b);
};
/**
 * Gets the color of the cursor in remove mode
 * @return {THREE.Vector3} A vector that represents the color of the cursor in remove mode
 */
SKULPT.GpuSkulpt.prototype.getCursorRemoveColor = function (r, g, b) {
    return this.__cursorRemoveColor;
};
/**
 * Sets the color of the cursor in remove mode
 * @param {number} r Red floating-point value between 0 and 1, inclusive
 * @param {number} g Green floating-point value between 0 and 1, inclusive
 * @param {number} b Blue floating-point value between 0 and 1, inclusive
 */
SKULPT.GpuSkulpt.prototype.setCursorRemoveColor = function (r, g, b) {
    this.__cursorRemoveColor.copy(r, g, b);
};
SKULPT.GpuSkulpt.prototype.__init = function () {
    this.__checkExtensions();
    this.__setupShaders();
    this.__setupRttScene();
    this.__setupVtf();

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

    //clear some of the render targets that are used as texture inputs for GLSL shaders
    //have to do this because a THREE.WebGLRenderTarget does not seem to be empty when first created in FireFox
    this.__rttQuadMesh.material = this.__clearMaterial;
    this.__clearMaterial.uniforms['uColor'].value.set(0.0, 0.0, 0.0, 0.0);
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__imageDataTexture, false);
};
SKULPT.GpuSkulpt.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);
    }
};
SKULPT.GpuSkulpt.prototype.__setupShaders = function () {

    this.__skulptMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uBaseTexture: { type: 't', value: null },
            uSculptTexture1: { type: 't', value: null },
            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) },
            uIsSculpting: { type: 'i', value: 0 },
            uSculptType: { type: 'i', value: 0 },
            uSculptPos: { type: 'v2', value: new THREE.Vector2() },
            uSculptAmount: { type: 'f', value: 0.05 },
            uSculptRadius: { type: 'f', value: 0.0 }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['skulpt']
    });

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

    this.__clearMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uColor: { type: 'v4', value: new THREE.Vector4() }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['setColor']
    });

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

    this.__rttProxyMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: null },
            uScale: { type: 'f', value: 0 }
        },
        vertexShader: this.__shaders.vert['passUv'],
        fragmentShader: this.__shaders.frag['scaleAndFlipV']
    });

    this.__channelVectors = {
        'r': new THREE.Vector4(1, 0, 0, 0),
        'g': new THREE.Vector4(0, 1, 0, 0),
        'b': new THREE.Vector4(0, 0, 1, 0),
        'a': new THREE.Vector4(0, 0, 0, 1)
    };
};
//Sets up the render-to-texture scene (2 render targets for accumulative feedback)
SKULPT.GpuSkulpt.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.__skulptMaterial);
    this.__rttScene.add(this.__rttQuadMesh);

    //create RTT render targets (we need two to do feedback)
    if (this.__supportsTextureFloatLinear) {
        this.__rttRenderTarget1 = new THREE.WebGLRenderTarget(this.__res, this.__res, this.__linearFloatRgbParams);
    } else {
        this.__rttRenderTarget1 = new THREE.WebGLRenderTarget(this.__res, this.__res, this.__nearestFloatRgbParams);
    }
    this.__rttRenderTarget1.generateMipmaps = false;
    this.__rttRenderTarget2 = this.__rttRenderTarget1.clone();

    //create a RTT render target for storing the combine results of all layers
    this.__rttCombinedLayer = this.__rttRenderTarget1.clone();

    //create RTT render target for storing proxy terrain data
    if (this.__supportsTextureFloatLinear) {
        this.__rttProxyRenderTarget = new THREE.WebGLRenderTarget(this.__proxyRes, this.__proxyRes, this.__linearFloatRgbParams);
    } else {
        this.__rttProxyRenderTarget = new THREE.WebGLRenderTarget(this.__proxyRes, this.__proxyRes, this.__nearestFloatRgbParams);
    }
    this.__rttProxyRenderTarget.generateMipmaps = false;

    //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;
};
//Sets up the vertex-texture-fetch for the given mesh
SKULPT.GpuSkulpt.prototype.__setupVtf = function () {
    this.__mesh.material = new THREE.ShaderMaterial({
        uniforms: THREE.UniformsUtils.merge([
            THREE.UniformsLib['lights'],
            THREE.UniformsLib['shadowmap'],
            {
                uTexture: { type: 't', value: null },
                uTexelSize: { type: 'v2', value: new THREE.Vector2(1.0 / this.__res, 1.0 / this.__res) },
                uTexelWorldSize: { type: 'v2', value: new THREE.Vector2(this.__gridSize, this.__gridSize) },
                uHeightMultiplier: { type: 'f', value: 1.0 },
                uBaseColor: { type: 'v3', value: new THREE.Vector3(0.6, 0.8, 0.0) },
                uShowCursor: { type: 'i', value: 0 },
                uCursorPos: { type: 'v2', value: new THREE.Vector2() },
                uCursorRadius: { type: 'f', value: 0.0 },
                uCursorColor: { type: 'v3', value: new THREE.Vector3() }
            }
        ]),
        vertexShader: this.__shaders.vert['heightMap'],
        fragmentShader: this.__shaders.frag['lambertCursor'],
        lights: true
    });
};
/**
 * Updates the skulpt<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
 */
SKULPT.GpuSkulpt.prototype.update = function (dt) {

    //have to set flags from other places and then do all steps at once during update

    //clear sculpts if necessary
    if (this.__shouldClear) {
        this.__rttQuadMesh.material = this.__clearMaterial;
        this.__clearMaterial.uniforms['uColor'].value.set(0.0, 0.0, 0.0, 0.0);
        this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTarget1, false);
        this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTarget2, false);
        this.__shouldClear = false;
        this.__updateCombinedLayers = true;
    }

    //do the main sculpting
    if (this.__isSculpting) {
        this.__rttQuadMesh.material = this.__skulptMaterial;
        this.__skulptMaterial.uniforms['uBaseTexture'].value = this.__imageDataTexture;
        this.__skulptMaterial.uniforms['uSculptTexture1'].value = this.__rttRenderTarget2;
        this.__skulptMaterial.uniforms['uIsSculpting'].value = this.__isSculpting;
        this.__skulptMaterial.uniforms['uSculptPos'].value.copy(this.__sculptUvPos);
        this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttRenderTarget1, false);
        this.__swapRenderTargets();
        this.__isSculpting = false;
        this.__updateCombinedLayers = true;
    }

    //combine layers into one
    if (this.__updateCombinedLayers) {  //this can be triggered somewhere else without sculpting

        this.__rttQuadMesh.material = this.__combineTexturesMaterial;
        this.__combineTexturesMaterial.uniforms['uTexture1'].value = this.__imageDataTexture;
        this.__combineTexturesMaterial.uniforms['uTexture2'].value = this.__rttRenderTarget2;
        this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttCombinedLayer, false);
        this.__updateCombinedLayers = false;

        //need to rebind rttCombinedLayer to uTexture
        this.__mesh.material.uniforms['uTexture'].value = this.__rttCombinedLayer;

        //check for the callback of type 'update'
        if (this.__callbacks.hasOwnProperty('update')) {
            var renderCallbacks = this.__callbacks['update'];
            var i, len;
            for (i = 0, len = renderCallbacks.length; i < len; i++) {
                renderCallbacks[i]();
            }
        }
    }
};
SKULPT.GpuSkulpt.prototype.__swapRenderTargets = function () {
    var temp = this.__rttRenderTarget1;
    this.__rttRenderTarget1 = this.__rttRenderTarget2;
    this.__rttRenderTarget2 = temp;
    // this.__skulptMaterial.uniforms['uSculptTexture1'].value = this.__rttRenderTarget2;
};
/**
 * Sets brush size
 * @param {number} size Brush size
 */
SKULPT.GpuSkulpt.prototype.setBrushSize = function (size) {
    var normSize = size / (this.__size * 2.0);
    this.__skulptMaterial.uniforms['uSculptRadius'].value = normSize;
    this.__mesh.material.uniforms['uCursorRadius'].value = normSize;
};
/**
 * Sets brush amount
 * @param {number} amount Brush amount
 */
SKULPT.GpuSkulpt.prototype.setBrushAmount = function (amount) {
    this.__skulptMaterial.uniforms['uSculptAmount'].value = amount;
};
/**
 * Loads terrain heights from image data
 * @param  {array} data Image data from canvas
 * @param  {number} amount Height multiplier
 * @param  {boolean} midGreyIsLowest Whether mid grey is considered the lowest part of the image
 */
SKULPT.GpuSkulpt.prototype.loadFromImageData = function (data, amount, midGreyIsLowest) {

    //convert data from Uint8ClampedArray to Float32Array so that DataTexture can use
    var normalizedHeight;
    var min = 99999;
    var i, len;
    for (i = 0, len = this.__imageProcessedData.length; i < len; i++) {
        if (midGreyIsLowest) {
            normalizedHeight = Math.abs(data[i] / 255.0 - 0.5);
        } else {
            normalizedHeight = data[i] / 255.0;
        }
        this.__imageProcessedData[i] = normalizedHeight * amount;

        //store min
        if (this.__imageProcessedData[i] < min) {
            min = this.__imageProcessedData[i];
        }
    }

    //shift down so that min is at 0
    for (i = 0, len = this.__imageProcessedData.length; i < len; i++) {
        this.__imageProcessedData[i] -= min;
    }

    //assign data to DataTexture
    this.__imageDataTexture.image.data = this.__imageProcessedData;
    this.__imageDataTexture.needsUpdate = true;
    this.__skulptMaterial.uniforms['uBaseTexture'].value = this.__imageDataTexture;
    this.__combineTexturesMaterial.uniforms['uTexture1'].value = this.__imageDataTexture;
    // this.__mesh.material.uniforms['uBaseTexture'].value = this.__imageDataTexture;
    this.__updateCombinedLayers = true;
};
/**
 * Sculpt the terrain
 * @param  {enum} type Sculpt operation type: SKULPT.GpuSkulpt.ADD, SKULPT.GpuSkulpt.REMOVE
 * @param  {THREE.Vector3} position World-space position to sculpt at
 * @param  {number} amount Amount to sculpt
 */
SKULPT.GpuSkulpt.prototype.sculpt = function (type, position, amount) {
    this.__skulptMaterial.uniforms['uSculptType'].value = type;
    this.__isSculpting = true;
    this.__sculptUvPos.x = (position.x + this.__halfSize) / this.__size;
    this.__sculptUvPos.y = (position.z + this.__halfSize) / this.__size;
    if (type === 1) {
        this.__mesh.material.uniforms['uCursorColor'].value.copy(this.__cursorAddColor);
    } else if (type === 2) {
        this.__mesh.material.uniforms['uCursorColor'].value.copy(this.__cursorRemoveColor);
    }
};
/**
 * Clears all sculpts
 */
SKULPT.GpuSkulpt.prototype.clear = function () {
    this.__shouldClear = true;
};
/**
 * Updates the cursor position
 * @param  {THREE.Vector3} position World-space position to update the cursor to
 */
SKULPT.GpuSkulpt.prototype.updateCursor = function (position) {
    this.__sculptUvPos.x = (position.x + this.__halfSize) / this.__size;
    this.__sculptUvPos.y = (position.z + this.__halfSize) / this.__size;
    this.__mesh.material.uniforms['uCursorPos'].value.set(this.__sculptUvPos.x, this.__sculptUvPos.y);
    this.__mesh.material.uniforms['uCursorColor'].value.copy(this.__cursorHoverColor);
};
/**
 * Shows the sculpt cursor
 */
SKULPT.GpuSkulpt.prototype.showCursor = function () {
    this.__mesh.material.uniforms['uShowCursor'].value = 1;
};
/**
 * Hides the sculpt cursor
 */
SKULPT.GpuSkulpt.prototype.hideCursor = function () {
    this.__mesh.material.uniforms['uShowCursor'].value = 0;
};
/**
 * Gets the sculpt texture that is used for displacement of mesh
 * @return {THREE.WebGLRenderTarget} Sculpt texture that is used for displacement of mesh
 */
SKULPT.GpuSkulpt.prototype.getSculptDisplayTexture = function () {
    return this.__rttCombinedLayer;
};
//Returns the pixel unsigned byte data for the render target texture (readPixels() can only return unsigned byte data)
SKULPT.GpuSkulpt.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);

};
SKULPT.GpuSkulpt.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);
};
/**
 * Gets float data for every pixel of the terrain texture<br/><strong>NOTE: This is an expensive operation.</strong>
 * @return {Float32Array} Float data of every pixel of the terrain texture
 */
SKULPT.GpuSkulpt.prototype.getPixelFloatData = function () {

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

    //cast to float
    var pixelFloatData = new Float32Array(this.__pixelByteData.buffer);
    return pixelFloatData;
};
/**
 * Gets float data for every pixel of the proxy terrain texture<br/><strong>NOTE: This is an expensive operation.</strong>
 * @return {Float32Array} Float data of every pixel of the proxy terrain texture
 */
SKULPT.GpuSkulpt.prototype.getProxyPixelFloatData = function () {

    //render to proxy render target
    this.__rttQuadMesh.material = this.__rttProxyMaterial;
    this.__rttProxyMaterial.uniforms['uTexture'].value = this.__rttCombinedLayer;
    this.__rttProxyMaterial.uniforms['uScale'].value = this.__actualToProxyRatio;
    this.__renderer.render(this.__rttScene, this.__rttCamera, this.__rttProxyRenderTarget, false);

    //get the encoded byte data first
    this.__getPixelEncodedByteData(this.__rttProxyRenderTarget, this.__proxyPixelByteData, 'r', this.__proxyRes, this.__proxyRes);

    //cast to float
    var pixelFloatData = new Float32Array(this.__proxyPixelByteData.buffer);
    return pixelFloatData;
};
/**
 * Adds callback function that are executed at specific times
 * @param {string} type Type of callback: 'update' (only choice available now)
 * @param {function} callbackFn Callback function
 */
SKULPT.GpuSkulpt.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');
    }
};

/**
 * Add sculpt operation
 * @const
 */
SKULPT.ADD = 1;
/**
 * Remove sculpt operation
 * @const
 */
SKULPT.REMOVE = 2;