Home Reference Source Test

src/Gisplay/Helpers/WebglUtils.js

/**
 * Class with static methods that will help with WebGL related stuff(Matrices, web mercator projection and shaders).
 * Always remeber WebGL is column major when reading the matrix code.
 * @see http://ptgmedia.pearsoncmg.com/images/chap3_9780321902924/elementLinks/03fig27.jpg
 * @static 
 * @class WebGLUtils
 */
export class WebGLUtils {

    /**
     * Calculates the scale and offset(X and Y) for the Web Mercator projection.
     * @static
     * @param {number} longitudeCenter - Longitude of the given position.
     * @param {number} latitudeCenter - Latitude of the given position.
     * @param {number} zoom - Current zoom level of the background map.
     * @param {number} tileSize - The size of each tile in the background map. Usually is 256. If different should be given in the API options.
     * @param {number} width - Width of the current canvas.
     * @param {number} height - Height of the current canvas.
     * @returns {{scale: number, offsetX: number, offsetY: number}} - Returns the scale, offsetX and offsetY of the point given using the Web Mercator projection.
     * @see https://bl.ocks.org/enjalot/fb7f3d696167e9b83a72#viewport.js
     * @see https://en.wikipedia.org/wiki/Web_Mercator
     * @memberOf WebGLUtils
     */
    static webMercatorProjection(longitudeCenter, latitudeCenter, zoom, tileSize, width, height) {
        // console.log(longitudeCenter, latitudeCenter, zoom, tileSize,  width, height);
        let PI = Math.PI;
        let scale = ((tileSize / 2) / PI) * Math.pow(2, zoom);
        let lambda = longitudeCenter * (PI / 180); // Convert longitude to radians
        let phi = latitudeCenter * (PI / 180); // Convert latitude to radians

        let xCenter = scale * (lambda + PI);
        let yCenter = scale * (PI - Math.log(Math.tan((PI / 4) + (phi / 2))));
        let offsetX = (width / 2) - xCenter;
        let offsetY = (height / 2) - yCenter;


        return { scale: scale, offsetX: offsetX, offsetY: offsetY };
    }

    /**
     * This is the result matrix from the multiplication of M1*M2*M3
     * @static
     * @param {number} scale - The scale calculated with WebMercator projection.
     * @param {number} width - The width of the canvas.
     * @param {number} height - The height of the canvas.
     * @param {number} offsetX - The offsetX calculated with WebMercator projection.
     * @param {number} offsetY - The offsetY calculated with WebMercator projection.
     * @returns {Float32Array} The resulting matrix (M1*M2*M3) in a single matrix to send to WebGL in order to calculate the resulting position.
     * @see Rui's thesis
     * @memberOf WebGLUtils
     */
    static finalMatrix(scale, width, height, offsetX, offsetY) {
        let p0 = (2 * Math.PI * scale) / (width * 180);
        let p2 = ((2 * Math.PI * scale) / width) + ((2 * offsetX) / width) - 1;
        let p4 = (2 * scale) / height;
        let p5 = ((2 * offsetY / height) - 1);
        return new Float32Array([
            p0, 0, 0,
            0, p4, 0,
            p2, p5, 1
        ]);
    }

    /**
     * Creates shaders(Vertex + Fragment) source code.
     * @static
     * @returns {{vertexCode: string, fragmentCode: string}} - The code for the vertex and fragment shaders.
     * @memberOf WebGLUtils
     */
    static generateShadersSourceCode() {
        let vertexSourceCode =
            `
            #define PI radians(180.0)

            attribute vec2 coords;
            uniform mat3 M;
            
         	attribute float aPointSize; 
         	attribute float a_opacity; 
         	varying float v_opacity; 

         	void main() {
                float phi = coords[1] * (PI / 180.0);
                float YValue = PI -log( tan((PI/4.0) + phi/2.0) );
                vec3 f = vec3(coords[0], YValue, 1.0);
                vec3 pixeis = M * f;
                float X = pixeis[0];
                float Y = -(pixeis[1]);
                gl_Position = vec4(X, Y , 0.0, 1.0);
         		
         		gl_PointSize = aPointSize; 
                v_opacity = a_opacity; 
            }
        `;

        let fragmentSourceCode =
            ` 
            precision mediump float;
         	uniform vec4 u_color;
         	varying float v_opacity; 
           	uniform float isPoint;
            void main() {
         		float border = 0.5;
         		float radius = 0.5;
         		float centerDist = length(gl_PointCoord - 0.5);
         		float alpha;
         		if (u_color[3] == -1.0)    
         			alpha =  v_opacity * step(centerDist, radius);
         		else 
         			alpha =  u_color[3] * step(centerDist, radius);

         		if(isPoint == 1.0 ) {
         		    if (alpha < 0.1) discard;
         			    gl_FragColor = vec4(u_color[0], u_color[1], u_color[2], alpha);
                }
           		else
         			gl_FragColor = vec4(u_color[0], u_color[1], u_color[2], u_color[3]);
         	}
        `;
        return { vertexCode: vertexSourceCode, fragmentCode: fragmentSourceCode };
    }

    /**
     * Creates and compiles a shader.
     * @static
     * @param {string} type - Type of shader. Options are: VERTEX_SHADER or FRAGMENT_SHADER;
     * @param {string} source_code - The shader source code.
     * @param {Map#_webgl} webgl - Webgl object used by the Map class.
     * @returns {WebGLShader} - The shader(vertex of fragment).
     * @memberOf WebGLUtils
     */
    static createAndCompileShader(type, source_code, webgl) {
        let shader = webgl.gl.createShader(type);
        webgl.gl.shaderSource(shader, source_code);
        webgl.gl.compileShader(shader);
        return shader;
    }

    /**
     * Initializes:
     * 1)WebGLProgram, 2) Generates shadders, 3) Attaches shaders to the program, 4) links program, 5) uses program.
     * @static
     * @param {{gl: WebGLRenderingContext, program: WebGLProgram}} webgl 
     * @returns {void}
     * @memberOf WebGLUtils
     */
    static createWebGLProgram(webgl) {
        webgl.program = webgl.gl.createProgram();

        const source_code = this.generateShadersSourceCode();
        const vertex_shader = this.createAndCompileShader(webgl.gl.VERTEX_SHADER, source_code.vertexCode, webgl);
        const fragment_shader = this.createAndCompileShader(webgl.gl.FRAGMENT_SHADER, source_code.fragmentCode, webgl);

        webgl.gl.attachShader(webgl.program, vertex_shader);
        webgl.gl.attachShader(webgl.program, fragment_shader);

        webgl.gl.linkProgram(webgl.program);
        webgl.gl.useProgram(webgl.program);
    }
}