Source: WebGL-Editor-utils.js

/**
 * @description Access to the WebGLRenderingContext interface.
 * @readonly
 * @type {WebGLRenderingContext}
 */
var gl;
/**
 * @description The HTMLCanvasElement.
 * @readonly
 * @type {HTMLCanvasElement}
 */
var canvas;
/**
 * @description Indicates if an object is currently loading or not. True when loading, otherwise false.
 * @readonly
 */
var _OBJisLoading = false;
/**
 * @description Indicates if an texture is currently loading or not. True when loading, otherwise false.
 * @readonly
 */
var _TEXisLoading = false;
/**
 * @instance controller
 * @description The main controller Object which handles mouse, keyboard and touch events.
 *
 * <pre>
 *     Mouse:
 *     Left-Mousebutton and drag    | Rotation
 *     Right-Mousebutton and drag   | Translation
 *     Mousewheel                   | Zoom/Object scaling
 *
 *     Keyboard:
 *     Arrow-Keys                   | Translation
 *     +/- Keys                     | Zoom/Object scaling
 *
 *     Touch-Gestures:
 *     Touch and drag               | Rotation
 *     Double-Tap and drag          | Translation
 *     Two-finger pinch             | Zoom/Object scaling
 *
 *     </pre>
 *
 * @type {Object}
 */
var controller;
/*---------------Further JsDoc for the used controller. All Variables can be accessed via controller.[variableName]*/
/**
 * @extends controller
 * @var controller:xRot
 * @description The overall amount of x-Rotation of all interactions.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:yRot
 * @description The overall amount of y-Rotation of all interactions.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:xTrans
 * @description The overall amount of x-Translation of all interactions.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:yTrans
 * @description The overall amount of y-Translation of all interactions.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:zTrans
 * @description The overall amount of z-Translation of all interactions.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:velocity
 * @description Sets the sensitivity of interactions. Typical values are 1/x, where x is a number.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:deltaXRot
 * @description The delta x-Rotation of the current interaction.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:deltaYRot
 * @description The delta y-Rotation of the current interaction.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:deltaXTrans
 * @description The delta x-Translation of the current interaction.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:deltaYTrans
 * @description The delta y-Translation of the current interaction.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:deltaZTrans
 * @description The delta z-Translation of the current interaction.
 * @type {Number}
 *
 */
/**
 * @extends controller
 * @var controller:objectScale
 * @description The scaling factor.
 * @type {Number}
 *
 */

//******************Creating an EDITOR module, it maintains a private internal state using the closure of the anonymous function***********************//
(function (scope) {
    'use strict';
    var EDITOR = {};
    scope.EDITOR = EDITOR;
    EDITOR._run = false;
    EDITOR.gl_requestId = 0;
    EDITOR._error = false;
    EDITOR._shaderError = false;
    EDITOR._pause = false;
    EDITOR.selectedTab = 0;
    EDITOR._pausedByMenu = false;

    EDITOR.initEditor = function () {
        canvas = document.getElementById("gl-canvas");
        $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', onFullScreenChange);
        //INITIALIZE Render-Monitor
        EDITOR.stats = new Stats();
        EDITOR.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
        document.getElementById("canvasBody").appendChild(EDITOR.stats.dom);
        //initialize the controller object
        controller = new Controller(canvas);
        //initialize the Spinner object to animate a pending download of objects and textures
        EDITOR._spinner = new Spinner({
            lines: 13 // The number of lines to draw
            , length: 28 // The length of each line
            , width: 10 // The line thickness
            , radius: 42 // The radius of the inner circle
            , scale: 1 // Scales overall size of the spinner
            , corners: 1 // Corner roundness (0..1)
            , color: '#999' // #rgb or #rrggbb or array of colors
            , opacity: 0.25 // Opacity of the lines
            , rotate: 23 // The rotation offset
            , direction: 1 // 1: clockwise, -1: counterclockwise
            , speed: 1.7 // Rounds per second
            , trail: 60 // Afterglow percentage
            , fps: 20 // Frames per second when using setTimeout() as a fallback for CSS
            , zIndex: 2e9 // The z-index (defaults to 2000000000)
            , className: 'spinner' // The CSS class to assign to the spinner
            , top: '49%' // Top position relative to parent
            , left: '50%' // Left position relative to parent
            , shadow: true // Whether to render a shadow
            , hwaccel: false // Whether to use hardware acceleration
            , position: 'absolute' // Element positioning
        });
        initCtx();
    };

    function initCtx() {
        //gl is the rendering context
        var ctx = WebGLUtils.setupWebGL(canvas, {preserveDrawingBuffer: true});
        gl = WebGLDebugUtils.makeDebugContext(ctx);
        gl = WebGLDebugUtils.makeDebugContext(gl, throwOnGLError, validateNoneOfTheArgsAreUndefined);
        if (!gl) {
            EDITOR.writeError("WebGL context isn't available");
        } else {
            EDITOR.writeCompletionMessage("WebGL context initialized successfully");
            // resizeCanvas();
            EDITOR.isMobile = false;
            // device detection
            if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)
                || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0, 4))) EDITOR.isMobile = true;

            if (!EDITOR.isMobile) {
                EDITOR.writeSystemMessage("CURRENT DEVICE: Desktop-PC");
                //allowing keyboard input onto canvas only for desktop devices
                canvas.contentEditable = true;
            } else {
                EDITOR.writeSystemMessage("CURRENT DEVICE: MOBILE");
            }
            EDITOR.writeSystemMessage("VENDOR: " + gl.getParameter(gl.VENDOR));
            EDITOR.writeSystemMessage("VERSION: " + gl.getParameter(gl.VERSION));
        }
        //configure WebGL
        gl.clearColor(0.3, 0.3, 0.3, 1);
        //set the viewport
        gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
        initAce();
    }

    function initAce() {
        EDITOR.webglEditor = ace.edit("editorWebGL");
        initEditor(EDITOR.webglEditor, 1);
        EDITOR.vsEditor = ace.edit("editorVS");
        initEditor(EDITOR.vsEditor, 0);
        EDITOR.fsEditor = ace.edit("editorFS");
        initEditor(EDITOR.fsEditor, 0);

        if (localStorage.getItem("webgl") !== null && localStorage.getItem("vs") !== null && localStorage.getItem("fs") !== null) {
            EDITOR.writeSystemMessage("Restoring your last known program state. Please keep in mind that the program state is lost once you clean your browsers cache!");
            EDITOR.webglEditor.setValue(localStorage.getItem("webgl"), -1);
            EDITOR.vsEditor.setValue(localStorage.getItem("vs"), -1);
            EDITOR.fsEditor.setValue(localStorage.getItem("fs"), -1);
        } else {
            EDITOR.setEditorTextFromFile("./src/demo/ColorizedCube/vertexShader.vert", EDITOR.vsEditor);
            EDITOR.setEditorTextFromFile("./src/demo/ColorizedCube/fragmentShader.frag", EDITOR.fsEditor);
            EDITOR.setEditorTextFromFile("./src/demo/ColorizedCube/webgl.js", EDITOR.webglEditor);
        }
    }

    function initEditor(editor, type) {
        editor.setTheme("ace/theme/chaos");
        editor.setOptions({
            enableBasicAutocompletion: true,
            enableLiveAutocompletion: true,
            enableSnippets: true,
            showPrintMargin: false
        });
        editor.resize();
        if (type === 0) {
            editor.getSession().setMode("ace/mode/glsl");
        } else {
            editor.getSession().setMode("ace/mode/javascript");
        }
        editor.commands.addCommand({
            name: 'commentLineCommand',
            bindKey: {win: 'Ctrl-M', mac: 'Command-M'},
            exec: function (e) {
                e.toggleCommentLines();
            },
            readOnly: false
        });
    }

    EDITOR.processJs = function (e) {
        var file = e.target.result, results;
        results = file.split("\n");
        var res = "";
        for (var i = 1; i < results.length; i++) {
            res += results[i];
        }
        EDITOR.webglEditor.setValue(res,-1);
    };

    EDITOR.processVert = function (e) {
        var file = e.target.result, results;
        results = file.split("\n");
        var res = "";
        for (var i = 1; i < results.length; i++) {
            res += results[i];
        }
        EDITOR.vsEditor.setValue(res,-1);
    };

    EDITOR.processFrag = function (e) {
        var file = e.target.result, results;
        results = file.split("\n");
        var res = "";
        for (var i = 1; i < results.length; i++) {
            res += results[i];
        }
        EDITOR.fsEditor.setValue(res,-1);
    };

    /**
     * Requests file contents and writes them into editor.
     *
     * @param      {String}  fileName  The source file
     * @param      {(EDITOR.webglEditor|EDITOR.vsEditor|EDITOR.fsEditor)}  editor The target editor
     */
    EDITOR.setEditorTextFromFile = function (fileName, editor) {
        var request = new XMLHttpRequest();
        try {
            request.responseType = 'text';
        } catch (e) {
        }
        request.open("GET", fileName, true);
        request.onreadystatechange = function () {
            if (request.readyState === 4) {
                editor.setValue(request.responseText, -1);
            }
        };
        request.send();
    };

    EDITOR.fullscreen = function () {
        // go fullscreen, depending on Browser-Vendor
        if (canvas.requestFullscreen) {
            canvas.requestFullscreen();
        } else if (canvas.webkitRequestFullscreen) {
            canvas.webkitRequestFullscreen();
        } else if (canvas.mozRequestFullScreen) {
            canvas.mozRequestFullScreen();
        } else if (canvas.msRequestFullscreen) {
            canvas.msRequestFullscreen();
        }
    };

    function onFullScreenChange() {
        var fullscreenElement = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement;
        // if in fullscreen mode fullscreenElement won't be null
        if (fullscreenElement !== undefined && fullscreenElement !== null) {
            var deviceWidth = $(window).width();
            var deviceHeight = $(window).height();
            gl.canvas.width = deviceWidth;
            gl.canvas.height = deviceHeight;
            gl.viewport(0, 0, deviceWidth, deviceHeight);
            if (EDITOR._run && !EDITOR._pause)
                EDITOR.compileRun();
        }
        else {
            EDITOR.resizeCanvas();
            if (EDITOR._run && !EDITOR._pause)
                EDITOR.compileRun();
        }
    }

    EDITOR.resizeCanvas = function () {
        // Lookup the size the browser is displaying the canvas.
        var rect = canvas.getBoundingClientRect();
        var displayWidth = rect.width;
        var displayHeight = rect.height;
        // Make the canvas the same size
        canvas.width = displayWidth;
        canvas.height = displayHeight;
        gl.viewport(0, 0, displayWidth, displayHeight);
        $('connection').connections('update');
    };

    //-------------------------------------------------------ERROR HANDLING-----------------------------------------------------------//
     window.onerror = function (msg, url, line, col, error) {
        // Note that col & error are new to the HTML 5 spec and may not be
        // supported in every browser.  It worked for me in Chrome.
        var extra = !col ? '' : '\ncolumn: ' + col;
        extra += !error ? '' : '\nerror: ' + error;
        EDITOR.writeError(msg);
         if (msg.indexOf("render") === -1)
            EDITOR.writeError("Line: " + line + " " + extra);
        //Report this error in order to keep track of what pages have JS issues
        if (url !== '' && msg.indexOf("render") === -1)
            EDITOR.writeError("URL: " + url);
        var suppressErrorAlert = true;
        // If you return true, then error alerts (like in older versions of
        // Internet Explorer) will be suppressed.
        return suppressErrorAlert;
    };

    function validateNoneOfTheArgsAreUndefined(functionName, args) {
        for (var ii = 0; ii < args.length; ++ii) {
            if (args[ii] === undefined) {
                EDITOR.writeError("Undefined argument passed to gl." + functionName + "(" +
                    WebGLDebugUtils.glFunctionArgsToString(functionName, args) + ")");
            }
        }
    }

    function throwOnGLError(err, funcName, args) {
        var errorMsg;
        var error = WebGLDebugUtils.glEnumToString(err);
        switch (error) {
            case "gl.INVALID_OPERATION":
                errorMsg = "INVALID_OPERATION: The specified command is not allowed for the current state";
                break;
            case "gl.INVALID_ENUM":
                errorMsg = "INVALID_ENUM: An unacceptable value has been specified for an enumerated argument";
                break;
            case "gl.INVALID_VALUE":
                errorMsg = "INVALID_VALUE: A numeric argument is out of range";
                break;
            case "gl.INVALID_FRAMEBUFFER_OPERATION":
                errorMsg = "INVALID_FRAMEBUFFER_OPERATION: The currently bound framebuffer is not framebuffer complete when trying to render to or to read from it";
                break;
            case "gl.OUT_OF_MEMORY":
                errorMsg = "OUT_OF_MEMORY: Not enough memory is left to execute the command";
                break;
            case "gl.CONTEXT_LOST_WEBGL":
                errorMsg = "CONTEXT_LOST_WEBGL: WebGL context is lost";
                break;
        }
        EDITOR.writeError(errorMsg);
    }
//---------------------------------------------------------------------------------------------------------------------------//
    EDITOR.compileRun = function () {
        EDITOR.cleanLog();
        EDITOR.cancelRun();
        var webGLCode = EDITOR.webglEditor.getValue();
        //preprocessing the webgl.js code to prevent further errors.
        if (webGLCode.indexOf("EDITOR") !== -1) {
            EDITOR.writeError("Accessing the 'EDITOR'-Module is not allowed. Execution terminated.");
            EDITOR._run = true;
            return;
        } /*else if (webGLCode.indexOf("render") === -1) {
            EDITOR.writeError("Function 'render(){...}' not defined. This function will be executed once for each frame! Execution terminated.");
            EDITOR._run = true;
            return;
        } */else if (webGLCode.indexOf("lookAtArcballCamera") !== -1 && webGLCode.indexOf("modelInteraction") !== -1) {
            EDITOR.writeError("The usage of both functions 'lookAtArcballCamera' and 'modelInteraction' at one time is not allowed. Execution terminated.");
            EDITOR._run = true;
            return;
        }
        EDITOR._run = true;
        // canvas.focus();
        var script = document.createElement("script");
        script.setAttribute("id", "webGLCode");
        var container = document.getElementById("body");
        var text = document.createTextNode(webGLCode);
        script.appendChild(text);
        container.appendChild(script);
        if (!EDITOR._pause && !EDITOR._error && !EDITOR._shaderError) {
            EDITOR.gl_requestId = requestAnimationFrame(EDITOR.renderLoop);
        }
    };

    EDITOR.cancelRun = function () {
        if (EDITOR._run) {
            cancelAnimationFrame(EDITOR.gl_requestId);
            EDITOR.gl_requestId = 0;
            EDITOR._spinner.stop();
            EDITOR._error = false;
            EDITOR._shaderError = false;
            _OBJisLoading = false;
            _TEXisLoading = false;
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
            resetControllerState();
            var x = document.getElementById("body");
            var y = document.getElementById("webGLCode");
            if( y !== null){
                x.removeChild(y);
            }
            EDITOR._run = false;
            WebGLDebugUtils.resetToInitialState(gl);
            EDITOR._pause = false;
        }
    };

    EDITOR.pauseRun = function () {
        cancelAnimationFrame(EDITOR.gl_requestId);
        EDITOR.gl_requestId = 0;
        EDITOR._pause = true;
    };

    EDITOR.unPause = function () {
        EDITOR._pause = false;
        EDITOR.gl_requestId = requestAnimationFrame(EDITOR.renderLoop);
    };

    //You should let your WebGL-rendering callback be a requestAnimationFrame callback: if you do so,
    // the browser will take care of the complex details of animation scheduling for you.
    EDITOR.renderLoop = function () {
        if (!_TEXisLoading && !_OBJisLoading) {
            EDITOR.stats.begin();
            render();
            EDITOR.stats.end();
        }
        if (!EDITOR._error && !EDITOR._shaderError) {
            EDITOR.gl_requestId = requestAnimationFrame(EDITOR.renderLoop);
        }
    };

    EDITOR.scrollLoggerToBottom = function () {
        $('#errorLogContainer').stop().animate({
            scrollTop: $('#errorLogContainer')[0].scrollHeight
        }, 800);
    };

    EDITOR.styleShaderError = function (errorMsg) {
        var pos = errorMsg.lastIndexOf("ERROR");
        if (pos !== -1) {
            var restMsg = cut(errorMsg, pos);
            EDITOR.styleShaderError(restMsg);
        }
    };

    function cut(str, cutStart) {
        var tmp = str.substring(cutStart, str.length);
        tmp = tmp.replace("ERROR", "LINE");
        EDITOR.writeError(tmp);
        return str.substring(0, cutStart);
    }

    //red
    EDITOR.writeError = function (message) {
        var container = document.getElementById("errorLogContainer");
        var newP = document.createElement("p");
        newP.style.color = "red";
        cancelAnimationFrame(EDITOR.gl_requestId);
        EDITOR.gl_requestId = 0;
        EDITOR._error = true;
        EDITOR._spinner.stop();
        //falls ein shadererror entsteht, entstehen folgefehler in der WebGL applikation daher mĂĽssen diese nicht gezeigt werden.
        if (!EDITOR._shaderError) {
            var newBr = document.createElement("br");
            var time = new Date().toLocaleTimeString();
            var text = document.createTextNode(time + ":  " + message);
            newP.appendChild(text);
            newP.appendChild(newBr);
            container.appendChild(newP, container.lastChild);
            EDITOR.scrollLoggerToBottom();
        }
    };
    //yellow
    EDITOR.writeSystemMessage = function (message) {
        var container = document.getElementById("errorLogContainer");
        var newP = document.createElement("p");
        newP.style.color = "yellow";
        var newBr = document.createElement("br");
        var time = new Date().toLocaleTimeString();
        var text = document.createTextNode(time + ":  " + message);
        newP.appendChild(text);
        newP.appendChild(newBr);
        container.appendChild(newP, container.lastChild);
        EDITOR.scrollLoggerToBottom();
    };
    //green
    EDITOR.writeCompletionMessage = function (message) {
        var container = document.getElementById("errorLogContainer");
        var newP = document.createElement("p");
        newP.style.color = "green";
        var newBr = document.createElement("br");
        var time = new Date().toLocaleTimeString();
        var text = document.createTextNode(time + ":  " + message);
        newP.appendChild(text);
        newP.appendChild(newBr);
        container.appendChild(newP, container.lastChild);
        EDITOR.scrollLoggerToBottom();
    };

    EDITOR.cleanLog = function () {
        var container = document.getElementById("errorLogContainer");
        while (container.hasChildNodes()) {
            container.removeChild(container.childNodes[0]);
        }
    };

    window.onbeforeunload = function () {
        if (typeof(Storage) !== "undefined") {
            if (localStorage.getItem("webgl") !== null || localStorage.getItem("vs") !== null || localStorage.getItem("fs") !== null) {
                localStorage.removeItem("webgl");
                localStorage.removeItem("vs");
                localStorage.removeItem("fs");
            }
            //else add it
            localStorage.setItem("webgl", EDITOR.webglEditor.getValue());
            localStorage.setItem("vs", EDITOR.vsEditor.getValue());
            localStorage.setItem("fs", EDITOR.fsEditor.getValue());
            // Code for localStorage/sessionStorage.
        } else {
            EDITOR.writeSystemMessage("Your browser does not support local web storage! Changes will not be saved!");
            return "Do you want to leave this site?"; //Prevent Ctrl+W/leaving without saving
            // Sorry! No Web Storage support..
        }
    };

    document.onkeyup = function (e) {
        var e = e || window.event; // for IE to cover IEs window event-object
        e.preventDefault();
        if (e.altKey && e.which === 49) {
            $("#webGL").click();
        } else if (e.altKey && e.which === 50) {
            $("#vs").click();
        } else if (e.altKey && e.which === 51) {
            $("#fs").click();
        } else if (e.altKey && e.which === 82) {
            EDITOR.compileRun();
        }
    };

    document.onkeydown = function (e) {
        var e = e || window.event;
        if (e.ctrlKey) {
            var c = e.which || e.keyCode;//Get key code
            switch (c) {
                case 83://Block Ctrl+S
                case 9://Block Ctrl+W --Does not work in Chrome
                    e.preventDefault();
                    e.stopPropagation();
                    break;
            }
        }
    };

    //reads files as string and saves it in the zip folder
    EDITOR.textFromFile = function (url, fileName, folder) {
        $.ajax({
            url: url,
            type: 'get',
            async: false,
            success: function (data) {
                if (fileName === 'index.html') {
                    data = data.replace("//VSPLACEHOLDER", EDITOR.vsEditor.getValue());
                    data = data.replace("//FSPLACEHOLDER", EDITOR.fsEditor.getValue());
                    data = data.replace("//WEBGLPLACEHOLDER", EDITOR.webglEditor.getValue());
                }
                folder.file(fileName, data); //store the string
            }
        });
    };
    EDITOR.textFromFileRestore = function (url) {
        $.ajax({
            url: url,
            type: 'get',
            async: false,
            success: function (data) {
            }
        });
    };

    EDITOR.imageFromFile = function (folder, zip) {
        var count = EDITOR.img.length;
        EDITOR.img.forEach(function (fileName) {
            JSZipUtils.getBinaryContent('src/texture/' + fileName, function (err, data) {
                if (err) {
                    writeLog("Problem happened while loading image: " + url);
                } else {
                    folder.file(fileName, data, {binary: true});
                    count--;
                    if (count === 0) {
                        //saving with images
                        EDITOR.save(zip);
                        //deferred.resolve(zip);
                    }
                }
            });
        });
    };


    EDITOR.img = [];
    EDITOR.availableImages = ["amiga.jpg", "brick_bump.jpg", "brick_diffuse.jpg", "brick_rough.jpg",
        "bump.jpg", "cerberus.jpg", "circle-tex.jpg", "cloud.png", "crate.gif", "disturb.jpg", "earth.jpg",
        "lavatile.jpg", "metal.png", "perlin.png", "transition1.png", "transition2.png", "transition3.png", "transition4.png", "transition5.png",
        "transition6.png", "UV_Grid_Sm.jpg", "water.jpg", "water_normals.jpg", "vive.png"];
    EDITOR.findUsedImages = function () {
        var code = EDITOR.webglEditor.getValue();
        for (var i = 0; i < EDITOR.availableImages.length; i++) {
            if (code.indexOf(EDITOR.availableImages[i]) !== -1) {
                EDITOR.img.push(EDITOR.availableImages[i]);
            }
        }
    };

    EDITOR.obj = [];
    EDITOR.availableObjects = ["car.obj", "cerberus.obj", "character.obj", "cube.obj", "deer.obj", "dna.obj", "globe.obj", "male-Body.obj", "mill.obj",
        "n64.obj", "skeleton.obj", "teapot.obj", "tuna.obj", "webgl-logo.obj", "zeppelin.obj", "bunny.obj", "vive.obj"];
    EDITOR.findUsedObjects = function () {
        var code = EDITOR.webglEditor.getValue();
        for (var i = 0; i < EDITOR.availableObjects.length; i++) {
            if (code.indexOf(EDITOR.availableObjects[i]) !== -1) {
                EDITOR.obj.push(EDITOR.availableObjects[i]);
            }
        }
    };

    EDITOR.saveSubroutine = function (objFolder, textureFolder, zip) {
        EDITOR.obj.forEach(function (fileName) {
            var url = 'src/object/' + fileName;
            EDITOR.textFromFile(url, fileName, objFolder);
        });
        if (EDITOR.img.length > 0) {
            EDITOR.imageFromFile(textureFolder, zip);
        } else {
            //saving without images
            EDITOR.save(zip);
        }
    };

    EDITOR.save = function (zip) {
        if (EDITOR._run) {
            //saving the drawn canvas
            canvas.toBlobHD(function (blob) {
                zip.file("canvas.png", blob);
                writeLog("Creating zip file...");
                zip.generateAsync({type: "blob"}).then(function (content) {
                    // see FileSaver.js
                    EDITOR.writeCompletionMessage("Initializing download...");
                    EDITOR.writeSystemMessage("File saved to the default path defined in your browser settings.");
                    //Not possible to define the saving path with javascript, You need to change your own browser settings.
                    // This would enter the realm of privileged code, of which website content code cannot touch.
                    saveAs(content, "WebGL-Editor.zip");
                    document.getElementById("save_program").disabled = false;
                });
            }, "image/png");
        } else {
            writeLog("Creating zip file...");
            // see FileSaver.js
            zip.generateAsync({type: "blob"}).then(function (content) {
                EDITOR.writeCompletionMessage("Initializing download...");
                EDITOR.writeSystemMessage("File saved to the default path defined in your browser settings.");
                //Not possible to define the saving path with javascript, You need to change your own browser settings.
                // This would enter the realm of privileged code, of which website content code cannot touch.
                saveAs(content, "WebGL-Editor.zip");
                document.getElementById("save_program").disabled = false;
            });
        }
        EDITOR.obj = [];
        EDITOR.img = [];
    };

    function resetControllerState() {
        controller.onchange = null;
        controller.xRot = 0;
        controller.yRot = 0;
        controller.curX = 0;
        controller.curY = 0;
        controller.xTrans = 0;
        controller.yTrans = 0;
        controller.zTrans = 0;
        controller.velocity = 1 / 100;
        controller.deltaXRot = 0.0;
        controller.deltaYRot = 0.0;
        controller.deltaXTrans = 0.0;
        controller.deltaYTrans = 0.0;
        controller.deltaZTrans = 0.0;
        controller.objectScale = 1;
    }
})(this); //End of Anonymous Closure//


//---------------------------------GLOBAL Helper functions the user can use!--------------------------------//

/**
 * Takes in the WebGLRenderingContext and starts to compile, attach and link the Vertex- and Fragment Shader.
 * @param {WebGLRenderingContext} gl is the WebGLRenderingContext instance
 * @return {WebGLProgram} A WebGLProgram object that is a combination of two compiled WebGLShaders consisting of a vertex shader and
 * a fragment shader (both written in GLSL). These are then linked into a usable program.
 */
function initShaders(gl) {
    var failed = 0;
    var vertShdr = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertShdr, EDITOR.vsEditor.getValue());
    gl.compileShader(vertShdr);
    if (!gl.getShaderParameter(vertShdr, gl.COMPILE_STATUS)) {
        EDITOR._spinner.stop();
        EDITOR.writeError("Vertex shader failed to compile:");
        EDITOR.styleShaderError(gl.getShaderInfoLog(vertShdr));
        failed = -1;
        EDITOR._shaderError = true;
    }
    var fragShdr = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragShdr, EDITOR.fsEditor.getValue());
    gl.compileShader(fragShdr);
    if (!gl.getShaderParameter(fragShdr, gl.COMPILE_STATUS)) {
        EDITOR._spinner.stop();
        EDITOR.writeError("Fragment shader failed to compile:");
        EDITOR.styleShaderError(gl.getShaderInfoLog(fragShdr));
        failed = -1;
        EDITOR._shaderError = true;
    }
    if (failed !== -1) {
        var program = gl.createProgram();
        gl.attachShader(program, vertShdr);
        gl.attachShader(program, fragShdr);
        gl.linkProgram(program);
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            EDITOR.writeError("Shader program failed to link. ");
            EDITOR._shaderError = true;
            EDITOR._spinner.stop();
            EDITOR.writeError(gl.getProgramInfoLog(program));
            return -1;
        }
        EDITOR.writeCompletionMessage("Shaders compiled successfully");
    }
    return program;
}

/**
 * @description
 * Returns the vertex shader code in String representation provided in the VS form.
 *
 * @return {String} String representation of the vertex shader code.
 */
function getVertexShaderSource() {
    return EDITOR.vsEditor.getValue();
}

/**
 * @description
 * Returns the fragment shader code in String representation provided in the FS form.
 *
 * @return {String} String representation of the fragment shader code.
 */
function getFragmentShaderSource() {
    return EDITOR.fsEditor.getValue();
}

/**
 * @description
 * Takes in a javascript array and copies it into a Float32Array.
 *
 * @param {Array} m An array in javascript representation
 * @return {Float32Array} The newly created Float32Array
 */
function flatten(m) {
    var result = new Float32Array(m[0].length * m.length);
    var i, j;
    for (i = 0; i < m.length; i++) {
        for (j = 0; j < m[0].length; j++) {
            result[m[0].length * i + j] = m[i][j];
        }
    }
    return result;
}

/**
 * @description
 * Logging function which prints the value of any given Type to the status-window.
 *
 * @param {*} message The message to be displayed
 */
function writeLog(message) {
    var container = document.getElementById("errorLogContainer");
    var newP = document.createElement("p");
    var newBr = document.createElement("br");
    var time = new Date().toLocaleTimeString();
    var text = document.createTextNode(time + ":  " + message);
    newP.appendChild(text);
    newP.appendChild(newBr);
    container.appendChild(newP, container.lastChild);
    EDITOR.scrollLoggerToBottom();
}

/**
 * @description
 * Generates a mat4 viewing matrix and connects it with the control of an arcball camera, by using the Object literals of the controller Object.
 *
 *  <pre>
 * Note: In order to see the changes when interacting with the canvas, the provided matrix has to be send to the vertex shader (e.g. as an uniform)
 * in the render-loop.
 * </pre>
 *
 * @param {mat4} matrix Placeholder matrix in which the viewing matrix will be stored
 * @param {vec3} eye The starting camera position
 * @param {vec3} at The starting focus point
 * @param {vec3} up The starting roll of the camera
 */
function lookAtArcballCamera(matrix, eye, at, up) {
    var camera = createArcballCamera(eye, at, up);
    camera.view(matrix);
    controller.onchange = function () {
        if (!EDITOR._pause) {
            camera.pan([-controller.deltaXTrans / 5, -controller.deltaYTrans / 5]);
            camera.zoom(controller.deltaZTrans * 10);
            camera.rotate([-controller.deltaXRot / 2, -controller.deltaYRot / 2], [0, 0]);
            camera.view(matrix);
        }
    };
}

/**
 * @description
 * Takes in a mat4 model matrix and manipulates it by using the Object literals of the controller Object.
 *
 * <pre>
 * Note: In order to see the changes when interacting with the canvas, the provided matrix has to be send to the vertex shader (e.g. as an uniform)
 * in the render-loop.
 * </pre>

 *
 * @param {mat4} matrix Placeholder matrix in which the model matrix will be stored
 */
function modelInteraction(matrix) {
    var xRotMatrix = mat4.create();
    var yRotMatrix = mat4.create();
    var translationMatrix = mat4.create();
    var scaleMatrix = mat4.create();

    controller.onchange = function () {
        if (!EDITOR._pause) {
            //Clamp the x-Rotation (do not flip the model)
            /* if (controller.xRot >= Math.PI / 2) {
             controller.xRot = Math.PI / 2
             } else if (controller.xRot <= -Math.PI / 2) {
             controller.xRot = -Math.PI / 2;
             }*/
            //ROTATE
            mat4.fromYRotation(yRotMatrix, -controller.yRot);
            mat4.fromXRotation(xRotMatrix, -controller.xRot);
            mat4.multiply(matrix, xRotMatrix, yRotMatrix);

            //TRANSLATE
            mat4.fromTranslation(translationMatrix, vec3.fromValues(-controller.xTrans, controller.yTrans, 0));
            mat4.multiply(matrix, translationMatrix, matrix);

            //SCALE
            mat4.fromScaling(scaleMatrix, vec3.fromValues(controller.objectScale, controller.objectScale, controller.objectScale));
            mat4.multiply(matrix, scaleMatrix, matrix);
        }
    };
}