Ignore:
Timestamp:
11/04/16 13:48:43 (8 years ago)
Author:
Mathieu Morlighem
Message:

merged trunk-jpl and trunk for revision 21337

Location:
issm/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • issm/trunk

  • issm/trunk/src

  • issm/trunk/src/m/plot/webgl.js

    r20500 r21341  
    11/*This is where we have all our webgl relevant functionality for the plotting routines: */
    2 //{{{ GL Initialization
     2
     3//{{{ Canvas Initialization
     4function initCanvas(options) {
     5        //Initialize open Gl for each canvas, if needed:
     6        canvas = document.getElementById(options.getfieldvalue('canvasid'));
     7        //var canvas = document.getElementById(options.getfieldvalue('canvasid'));
     8        if (!canvas.initialized) {
     9                typedArraySliceSupport();
     10                canvas.gl = initWebGL(canvas,options);
     11                canvas.nodes = [];
     12                if (canvas.drawHandler) { window.cancelAnimationFrame(canvas.drawHandler); }
     13                draw(canvas,options);
     14                canvas.initialized = true;
     15        }
     16        return canvas;
     17}
    318function initWebGL(canvas,options) { //{{{
    4         gl = null;
    5 
     19        var gl;
    620        try {
    7                 // Try to grab the standard context. If it fails, fallback to experimental.
    8                 gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    9         }
    10         catch(e) {}
    11 
    12         // If we don't have a GL context, give up now
    13         if (!gl) {
    14                 alert("Unable to initialize WebGL. Your browser may not support it.");
    15                 gl = null;
    16         }
    17                
    18         // Enable depth testing
    19         gl.enable(gl.DEPTH_TEST);
    20         // Near things obscure far things
    21         gl.depthFunc(gl.LEQUAL);
    22         // Clear the color as well as the depth buffer.
    23         gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    24         // Enable color blending/overlay
    25         gl.enable(gl.BLEND);
    26 
    27         // Allocate arrays equal to maximium number of attributes used by any one shader
    28         gl.enableVertexAttribArray(0);
    29         gl.enableVertexAttribArray(1);
    30 
    31         // Load shaders and store them in gl object
    32         gl.shaders = loadShaders(gl);
     21                if (!canvas.gl) {
     22                        gl = GL.create({canvas:canvas});
     23                        // Enable depth testing
     24                        gl.enable(gl.DEPTH_TEST);
     25                        // Near things obscure far things
     26                        gl.depthFunc(gl.LEQUAL);
     27                        // Enable color blending/overlay
     28                        gl.enable(gl.BLEND);
     29                        // Enable face culling
     30                        gl.enable(gl.CULL_FACE);
     31                        gl.cullFace(gl.FRONT);
     32                        // Load shaders and store them in gl object
     33                        gl.shaders = loadShaders(gl,options.getfieldvalue('rootpath','../../../js/'));
     34                       
     35                        // Add event listeners for canvas
     36                        var displayview = options.getfieldvalue('displayview','off') == 'on';
     37                        var displayzoom = options.getfieldvalue('displayzoom','off') == 'on';
     38                        var mc = new Hammer.Manager(canvas);
     39                       
     40                        mc.add( new Hammer.Tap({ event: 'singletap' }) );
     41                        mc.add(new Hammer.Pan({threshold:0, pointers:0}));
     42                        mc.add(new Hammer.Pinch({threshold:0})).recognizeWith(mc.get('pan'));
     43                        mc.on('singletap', function (ev) {onTap(ev,canvas);});
     44                        mc.on('panstart panmove', function (ev) {onPan(ev,canvas,displayview);});
     45                        mc.on('pinchstart pinchmove', function (ev) {onPinch(ev,canvas,displayview);});
     46                       
     47                        //canvas.addEventListener('mousemove', function (ev) {onTap(ev,canvas);}, false);
     48                        canvas.addEventListener('mousewheel', function (ev) {onZoom(ev,canvas,displayzoom)}, false);
     49                        canvas.addEventListener('DOMMouseScroll', function (ev) {onZoom(ev,canvas,displayzoom)}, false);
     50                }
     51                else {
     52                        gl = canvas.gl;
     53                }
     54        }
     55        catch(e) {
     56                console.log(e);
     57                return;
     58        }
    3359       
    3460        // Add context state variables
    3561        //TODO:Group variables in objects for organization and naming
    36         canvas.zoomBounds = options.getfieldvalue('zoombounds',[0.001,100.0]);
    37         canvas.zoomFactor = clamp(options.getfieldvalue('zoomfactor',1.0), canvas.zoomBounds[0], canvas.zoomBounds[1]);
    38         canvas.zoomLast = canvas.zoomFactor;
     62        canvas.gl = gl;
     63        canvas.rootPath = options.getfieldvalue('rootpath','../../../js/');
     64        canvas.cameraPosition = vec3.create();
    3965        canvas.cameraMatrix = mat4.create();
    40         canvas.translation = options.getfieldvalue('centeroffset',[0,0,0.0]);
    41         canvas.rotationAzimuthBounds = options.getfieldvalue('azimuthbounds',[0,360]);
    42         canvas.rotationElevationBounds = options.getfieldvalue('elevationbounds',[-180,180]);
    43         canvas.rotationDefault = options.getfieldvalue('view',[0,90]); //0 azimuth - up is north, 90 elevation - looking straight down
    44         canvas.rotation = canvas.rotationDefault;
    45         canvas.controlsensitivity = 1;
    46         canvas.twod = options.getfieldvalue('2d','off') == 'on';
     66        canvas.controlSensitivity = options.getfieldvalue('controlsensitivity',1);
     67        canvas.dataMarkersAllowed = options.getfieldvalue('datamarkers','off') == 'on';
     68        canvas.dataMarkersEnabled = true; //if data marker feature is on, user can toggle feature on and off
     69        canvas.dataMarkerImage = options.getfieldvalue('datamarkers_image',canvas.rootPath+'textures/data_marker.svg');
     70        canvas.dataMarkerSize = options.getfieldvalue('datamarkerssize',[32,32]);
     71        canvas.dataMarkerOptions = options.getfieldvalue('datamarkersoptions',{'enabled':'on','image':canvas.rootPath+'textures/data_marker.svg','size':[32,32],'format':['X: %.2e<br>Y: %.2e<br>Z: %.2e]<br>Value: %0.1f','x','y','z','value']});
     72        canvas.inverseCameraMatrix = mat4.create();
     73        canvas.id = options.getfieldvalue('canvasid','.sim-canvas');
     74        canvas.movieFrame = 0;
    4775        canvas.moviePlay = true;
    4876        canvas.movieReverse = false;
    4977        canvas.movieIncrement = true;
    50 
    51         if (canvas.twod) {
    52                 canvas.rotationAzimuthBounds = [0,0];
    53                 canvas.rotationElevationBounds = [90,90];
    54                 canvas.rotationDefault = [0,90];
    55                 canvas.rotation = canvas.rotationDefault;
    56         }
    57        
    58         // Add event listeners for canvas
    59         canvas.addEventListener("mousewheel", function (ev) {onZoom(ev,canvas,options.getfieldvalue('displayzoom','off') == 'on')}, false);
    60         canvas.addEventListener("DOMMouseScroll", function (ev) {onZoom(ev,canvas,options.getfieldvalue('displayzoom','off') == 'on')}, false);
    61 
    62         var mc = new Hammer.Manager(canvas);
    63 
    64     mc.add(new Hammer.Pan({threshold:0, pointers:0}));
    65     mc.add(new Hammer.Pinch({threshold:0})).recognizeWith(mc.get('pan'));
    66 
    67     mc.on("panstart panmove", function (ev) {onPan(ev,canvas,options.getfieldvalue('displayview','off') == 'on');});
    68     mc.on("pinchstart pinchmove", function (ev) {onPinch(ev,canvas,options.getfieldvalue('displayview','off') == 'on');});
    69         //mc.on("mousewheel DOMMouseScroll", function (ev) {onZoom(ev,canvas,options);});
     78        canvas.movieOptions = options.getfieldvalue('movieoptions',{'fps':4,'loop':true});
     79        canvas.moviefps = options.getfieldvalue('moviefps',5);
     80        canvas.rotation = options.getfieldvalue('view',[0,90]); //0 azimuth, 90 elevation
     81        canvas.rotationAzimuthBounds = options.getfieldvalue('azlim',[0,360]);
     82        canvas.rotationElevationBounds = options.getfieldvalue('ellim',[-180,180]);
     83        canvas.translation = options.getfieldvalue('origin',[0,0,0]);
     84        canvas.twod = options.getfieldvalue('2d','off') == 'on';
     85        canvas.view = options.getfieldvalue('view',[0,90]);
     86        canvas.viewPanning = options.getfieldvalue('enablepanning','off') == 'on';
     87        canvas.vInverseMatrix = mat4.create();
     88        canvas.zoomBounds = options.getfieldvalue('zoomlim',[0.001,100.0]);
     89        canvas.zoom = clamp(options.getfieldvalue('zoom',1.0), canvas.zoomBounds[0], canvas.zoomBounds[1]);
     90        canvas.zoomLast = canvas.zoom;
     91        var backgroundcolor = new RGBColor(options.getfieldvalue('backgroundcolor','lightcyan'));
     92        if (backgroundcolor.ok) { canvas.backgroundcolor = [backgroundcolor.r/255.0, backgroundcolor.g/255.0, backgroundcolor.b/255.0, 1.0]; }
     93        else { throw Error(sprintf('s%s%s\n','initWebGL error message: cound not find out background color for curent canvas ',canvas)); }
    7094       
    7195        return gl;
    7296} //}}}
    73 function initBuffers(gl,arrays) { //{{{
    74         var bufferArray = [];
    75         for (var i = 0; i < arrays.length; i++) {
    76                 bufferArray[i] = gl.createBuffer();     
    77                 bufferArray[i].itemSize = arrays[i].itemSize;
    78                 bufferArray[i].numItems = arrays[i].length/bufferArray[i].itemSize;
    79                
    80                 if (bufferArray[i].itemSize > 1) {
    81                         gl.bindBuffer(gl.ARRAY_BUFFER, bufferArray[i]);
    82                         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(arrays[i]), gl.STATIC_DRAW);
    83                 }
    84                 else {
    85                         //TODO: identify index buffers uniquely (by name)
    86                         gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferArray[i]);
    87                         gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(arrays[i]), gl.STATIC_DRAW);
    88                 }
    89         }       
    90         return bufferArray;
     97function loadShaders(gl,rootPath) { //{{{
     98        var shaders = {};
     99        shaders.Colored = new GL.Shader.fromURL(rootPath+'shaders/Colored.vsh', rootPath+'shaders/Colored.fsh', null, gl);
     100        shaders.Textured = new GL.Shader.fromURL(rootPath+'shaders/Textured.vsh', rootPath+'shaders/Textured.fsh', null, gl);
     101        shaders.SkyFromSpace = new GL.Shader.fromURL(rootPath+'shaders/SkyFromSpace.vert', rootPath+'shaders/SkyFromSpace.frag', null, gl);
     102        shaders.GroundFromSpace = new GL.Shader.fromURL(rootPath+'shaders/GroundFromSpace.vert', rootPath+'shaders/GroundFromSpace.frag', null, gl);
     103        return shaders;
    91104} //}}}
    92105function initTexture(gl,imageSource) { //{{{
    93         var texture = gl.createTexture();
    94         texture.image = new Image();
    95         texture.isLoaded = false;
    96         texture.image.onload = function () {
    97                 handleLoadedTexture(gl,texture);
    98         }
    99         texture.image.src = imageSource;
    100         return texture;
    101 } //}}}
    102 function handleLoadedTexture(gl,texture) { //{{{
    103         gl.activeTexture(gl.TEXTURE0);
    104         gl.bindTexture(gl.TEXTURE_2D, texture);
    105         gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    106         gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
    107         gl.generateMipmap(gl.TEXTURE_2D);
    108         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR);
    109         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    110         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    111         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    112         gl.bindTexture(gl.TEXTURE_2D, null);
    113         texture.isLoaded = true;
    114 } //}}}
    115 function Node(gl,options) { //{{{
    116         //Returns a Node object that contains default display states for webgl object
    117         return {buffers:[],
    118                 shader:gl.shaders["colored"]["program"],
    119                 draw:null,
     106        return GL.Texture.fromURL(imageSource, {minFilter:gl.LINEAR_MIPMAP_LINEAR, magFilter:gl.LINEAR}, null, gl);
     107} //}}}
     108function Node(gl) { //{{{
     109        //Returns a Node object that contains default display states for webgl object. center represents pivot point of rotation.
     110        return {
     111                alpha:1.0,
     112                buffers:[],
     113                cullFace:gl.FRONT,
     114                disableDepthTest:false,
     115                drawMode:gl.TRIANGLES,
     116                drawOrder:0,
     117                enabled:true,
     118                enableCullFace:false,
    120119                hideOcean:false,
    121                 level:0,
    122                 useIndexBuffer:true,
    123                 alpha:1.0,
    124                 disableDepthTest:false,
    125                 enableCullFace:false,
    126                 cullFace:gl.FRONT,
    127                 drawMode:gl.TRIANGLES,
    128                 texture:null,
    129                 translation:vec3.create(),
    130                 rotation:vec3.fromValues(-90, 0, 0),
    131                 scale:vec3.fromValues(1, 1, 1),
    132                 modelMatrix:mat4.create(),
    133                 shaderName:"colored",
    134                 drawOrder:0,
     120                lineWidth:1.0,
    135121                maskEnabled:false,
    136122                maskHeight:150.0,
    137123                maskColor:vec4.fromValues(0.0, 0.0, 1.0, 1.0),
    138                 enabled:true,
     124                mesh:null,
     125                name:'node',
     126                shaderName:'Colored',
     127                shader:gl.shaders.Colored,
     128                texture:null,
     129                useIndexBuffer:true,
     130                center:vec3.create(),
     131                scale:vec3.fromValues(1, 1, 1),
     132                rotation:vec3.create(),
     133                translation:vec3.create(),
     134                modelMatrix:mat4.create(),
     135                rotationMatrix:mat4.create(),
     136                inverseModelMatrix:mat4.create(),
     137                inverseRotationMatrix:mat4.create()
    139138        };
    140139} //}}}
    141 function recalculateModelMatrix(node) { //{{{
    142         //TODO: move to 0,0,0, rotate,move back to normal space, then apply transform
     140function debugNodes(canvasid) { //{{{
     141        var canvasid = canvasid || '.sim-canvas';
     142        var nodes = $(canvasid)[0].nodes;
     143        console.log(canvasid, 'Nodes:');
     144        for (var node in nodes) {
     145                console.log('name: ', nodes[node].name, ' node: ', nodes[node], ' mesh: ', nodes[node].mesh, ' translation: ', nodes[node].translation, ' center:', nodes[node].center, ' rotation:', nodes[node].rotation);
     146        }
     147        return nodes;
     148} //}}}
     149function updateModelMatrix(node) { //{{{
    143150        var modelMatrix = mat4.create();
    144151
     152        var translationMatrix = mat4.create();
     153        mat4.translate(translationMatrix, translationMatrix, [-node.center[0],-node.center[1],-node.center[2]]); //scale/rotation centering
     154        mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
     155       
    145156        var scaleMatrix = mat4.create();
    146         mat4.scale(scaleMatrix, scaleMatrix, node["scale"]);
     157        mat4.scale(scaleMatrix, scaleMatrix, node.scale);
    147158        mat4.multiply(modelMatrix, scaleMatrix, modelMatrix);
    148 
    149         var translationMatrix = mat4.create();
    150         mat4.translate(translationMatrix, translationMatrix, node["translation"]); //relative translation
     159       
     160        var rotationMatrix = mat4.create();
     161        var zRotationMatrix = mat4.create();   
     162        mat4.rotate(zRotationMatrix, zRotationMatrix, DEG2RAD * node.rotation[2], [0.0, 0.0, 1.0]);
     163        mat4.multiply(rotationMatrix, zRotationMatrix, rotationMatrix);
     164        var yRotationMatrix = mat4.create();   
     165        mat4.rotate(yRotationMatrix, yRotationMatrix, DEG2RAD * node.rotation[1], [0.0, 1.0, 0.0]);
     166        mat4.multiply(rotationMatrix, yRotationMatrix, rotationMatrix);
     167        var xRotationMatrix = mat4.create();   
     168        mat4.rotate(xRotationMatrix, xRotationMatrix, DEG2RAD * node.rotation[0], [1.0, 0.0, 0.0]);
     169        mat4.multiply(rotationMatrix, xRotationMatrix, rotationMatrix);
     170        mat4.multiply(modelMatrix, rotationMatrix, modelMatrix);       
     171       
     172        mat4.identity(translationMatrix);
     173        mat4.translate(translationMatrix, translationMatrix, node.center); //relative translation
    151174        mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
    152175       
    153         var zRotationMatrix = mat4.create();   
    154         mat4.rotate(zRotationMatrix, zRotationMatrix, radians(node["rotation"][2]), [0.0, 0.0, 1.0]);
    155         mat4.multiply(modelMatrix, zRotationMatrix, modelMatrix);
    156         var yRotationMatrix = mat4.create();   
    157         mat4.rotate(yRotationMatrix, yRotationMatrix, radians(node["rotation"][1]), [0.0, 1.0, 0.0]);
    158         mat4.multiply(modelMatrix, yRotationMatrix, modelMatrix);
    159         var xRotationMatrix = mat4.create();   
    160         mat4.rotate(xRotationMatrix, xRotationMatrix, radians(node["rotation"][0]), [1.0, 0.0, 0.0]);
    161         mat4.multiply(modelMatrix, xRotationMatrix, modelMatrix);
    162 
    163         return modelMatrix;
    164 } //}}}
    165 function radians (degrees) { //{{{
    166   return degrees * Math.PI / 180;
    167 } //}}}
    168 function degrees (radians) { //{{{
    169   return radians * 180 / Math.PI;
     176        mat4.identity(translationMatrix);
     177        mat4.translate(translationMatrix, translationMatrix, node.translation); //absolute translation
     178        mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
     179       
     180        node.modelMatrix = modelMatrix;
     181        node.inverseModelMatrix = mat4.invert(mat4.create(), modelMatrix);
     182        node.rotationMatrix = rotationMatrix;
     183        node.inverseRotationMatrix = mat4.invert(mat4.create(), rotationMatrix);;
    170184} //}}}
    171185function clamp(value, min, max) { //{{{
    172186        return Math.max(min, Math.min(value, max));
    173187} //}}}
    174 function recoverview(canvasid,defaultview) { //{{{
    175         var canvas  = document.getElementById(canvasid);
    176         if (canvas && canvas.hasOwnProperty("rotation")) {
    177                 return canvas.rotation;
    178         }
    179         return defaultview;
    180 } //}}}
    181 function recovercenteroffset(canvasid,defaultcenter) { //{{{
    182         var canvas  = document.getElementById(canvasid);
    183         if (canvas && canvas.hasOwnProperty("translation")) {
    184                 return canvas.translation;
    185         }
    186         return defaultcenter;
    187 } //}}}
    188 //}}}
    189 //{{{ Shader Loading
    190 function loadShaders(gl) { //{{{
    191         var shaderNames = ["colored", "unlit_textured"];
    192         shaders = {};
    193         shaders["colored"] = {loaded:false, vsh:{}, fsh:{}};
    194         shaders["colored"]["vsh"]["string"] =
    195                 ['attribute vec3 aVertexPosition;',
    196                 'attribute vec4 aVertexColor;',
    197                 '',
    198                 'uniform mat4 uMVPMatrix;',
    199                 'uniform float uAlpha;',
    200                 '',
    201                 'varying vec4 vColor;',
    202                 '',
    203                 'void main(void) {',
    204                 '       gl_PointSize = 3.0;',
    205                 '       gl_Position = uMVPMatrix * vec4(aVertexPosition.xyz, 1.0);',
    206                 '       vColor = vec4(aVertexColor.xyz, uAlpha);',
    207                 '}'].join('\n');
    208         shaders["colored"]["fsh"]["string"] =
    209                 ['precision mediump float;',
    210                 '',
    211                 'varying vec4 vColor;',
    212                 '',
    213                 'void main(void) {',
    214                 '       gl_FragColor = vColor;',
    215                 '}'].join('\n');
    216         shaders["unlit_textured"] = {loaded:false, vsh:{}, fsh:{}};
    217         shaders["unlit_textured"]["vsh"]["string"] =
    218                 ['attribute vec3 aVertexPosition;',
    219                 'attribute vec2 aTextureCoord;',
    220                 '',
    221                 'uniform mat4 uMVPMatrix;',
    222                 '',
    223                 'varying vec2 vTextureCoord;',
    224                 'varying float vZCoord;',
    225                 '',
    226                 'void main(void) {',
    227                 '       gl_PointSize = 3.0;',
    228                 '       gl_Position = uMVPMatrix * vec4(aVertexPosition.xyz, 1.0);',
    229                 '       vTextureCoord = aTextureCoord;',
    230                 '       vZCoord = aVertexPosition.z;',
    231                 '}'].join('\n');
    232         shaders["unlit_textured"]["fsh"]["string"] =
    233                 ['precision mediump float;',
    234                 '',
    235                 'varying vec2 vTextureCoord;',
    236                 'varying float vZCoord;',
    237                 '',
    238                 'uniform sampler2D uColorSampler;',
    239                 'uniform float uAlpha;',
    240                 'uniform bool uMaskEnabled;',
    241                 'uniform float uMaskHeight;',
    242                 'uniform vec4 uMaskColor;',
    243                 '',
    244                 'void main(void) {',
    245                 '       if (uMaskEnabled && (vZCoord < uMaskHeight)) {',
    246                 '               gl_FragColor = vec4(uMaskColor.xyz, uAlpha);',
    247                 '       }',
    248                 '       else {',
    249                 '               gl_FragColor = vec4(texture2D(uColorSampler, vec2(vTextureCoord.s, vTextureCoord.t)).rgb, uAlpha);',
    250                 '       }',
    251                 '}'].join('\n');
    252         shaderNames.forEach(function(shaderName){
    253                 shaders[shaderName]["vsh"]["shader"] = getShaderByString(gl, shaders[shaderName]["vsh"]["string"], "vsh");
    254                 shaders[shaderName]["fsh"]["shader"] = getShaderByString(gl, shaders[shaderName]["fsh"]["string"], "fsh");
    255 
    256                 shaders[shaderName]["program"] = gl.createProgram();
    257                 gl.attachShader(shaders[shaderName]["program"], shaders[shaderName]["vsh"]["shader"]);
    258                 gl.attachShader(shaders[shaderName]["program"], shaders[shaderName]["fsh"]["shader"]);
    259                 gl.linkProgram(shaders[shaderName]["program"]);
    260 
    261                 if (!gl.getProgramParameter(shaders[shaderName]["program"], gl.LINK_STATUS)) {
    262                         alert("Could not initialise shaders");
    263                 }
    264 
    265                 var vshStringArray = shaders[shaderName]["vsh"]["string"].split("\n");
    266                 var fshStringArray = shaders[shaderName]["fsh"]["string"].split("\n");
    267                 var line = "";
    268                 var property = "";
    269                 for (var i = 0; i < vshStringArray.length; i++) {
    270                         line = vshStringArray[i];
    271                         if (line.search("attribute") != -1) {
    272                                 property = nameFromLine(line);
    273                                 shaders[shaderName]["program"][property] = gl.getAttribLocation(shaders[shaderName]["program"], property);
    274                         }
    275                         else if (line.search("uniform") != -1) {
    276                                 property = nameFromLine(line);
    277                                 shaders[shaderName]["program"][property] = gl.getUniformLocation(shaders[shaderName]["program"], property);
    278                         }
    279                         else if (line.search("void main") != -1) {
    280                                 break;
    281                         }
    282                 }
    283                 for (var i = 0; i < fshStringArray.length; i++) {
    284                         line = fshStringArray[i];
    285                         if (line.search("uniform") != -1) {
    286                                 property = nameFromLine(line);
    287                                 shaders[shaderName]["program"][property] = gl.getUniformLocation(shaders[shaderName]["program"], property);
    288                         }
    289                         else if (line.search("void main") != -1) {
    290                                 break;
    291                         }
    292                 }
    293                 shaders[shaderName]["loaded"] = true;
    294         });
    295         return shaders;
    296 } //}}}
    297 function getShaderByString(gl,str,type) { //{{{
    298         var shader;
    299         if (type == "fsh") {
    300                 shader = gl.createShader(gl.FRAGMENT_SHADER);
    301         }
    302         else if (type == "vsh") {
    303                 shader = gl.createShader(gl.VERTEX_SHADER);
    304         }
    305         else {
    306                 return null;
    307         }
    308        
    309         gl.shaderSource(shader, str);
    310         gl.compileShader(shader);
    311 
    312         if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {       
    313                 alert(gl.getShaderInfoLog(shader));
    314                 return null;
    315         }
    316 
    317         return shader;
    318 } //}}}
    319 function nameFromLine(line) { //{{{
    320         //returns lowerCamelCase property name from shader line
    321         var fullName = line.split(" ")[2];
    322         return fullName.slice(0, fullName.search(";"));
     188function recover(canvasid,name,defaultvalue) { //{{{
     189        var canvas = document.getElementById(canvasid);
     190        if (canvas && canvas.hasOwnProperty(name)) { return canvas[name]; }
     191        return defaultvalue;
     192} //}}}
     193function typedArraySliceSupport() { //{{{
     194        //TypedArray compatibility for Safari/IE
     195        if (typeof Int8Array !== 'undefined') {
     196                if (!Int8Array.prototype.fill) { Int8Array.prototype.fill = Array.prototype.fill; }
     197                if (!Int8Array.prototype.slice) { Int8Array.prototype.slice = Array.prototype.slice; }
     198        }
     199        if (typeof Uint8Array !== 'undefined') {
     200                if (!Uint8Array.prototype.fill) { Uint8Array.prototype.fill = Array.prototype.fill; }
     201                if (!Uint8Array.prototype.slice) { Uint8Array.prototype.slice = Array.prototype.slice; }
     202        }
     203        if (typeof Uint8ClampedArray !== 'undefined') {
     204                if (!Uint8ClampedArray.prototype.fill) { Uint8ClampedArray.prototype.fill = Array.prototype.fill; }
     205                if (!Uint8ClampedArray.prototype.slice) { Uint8ClampedArray.prototype.slice = Array.prototype.slice; }
     206        }
     207        if (typeof Int16Array !== 'undefined') {
     208                if (!Int16Array.prototype.fill) { Int16Array.prototype.fill = Array.prototype.fill; }
     209                if (!Int16Array.prototype.slice) { Int16Array.prototype.slice = Array.prototype.slice; }
     210        }
     211        if (typeof Uint16Array !== 'undefined') {
     212                if (!Uint16Array.prototype.fill) { Uint16Array.prototype.fill = Array.prototype.fill; }
     213                if (!Uint16Array.prototype.slice) { Uint16Array.prototype.slice = Array.prototype.slice; }
     214        }
     215        if (typeof Int32Array !== 'undefined') {
     216                if (!Int32Array.prototype.fill) { Int32Array.prototype.fill = Array.prototype.fill; }
     217                if (!Int32Array.prototype.slice) { Int32Array.prototype.slice = Array.prototype.slice; }
     218        }
     219        if (typeof Uint32Array !== 'undefined') {
     220                if (!Uint32Array.prototype.fill) { Uint32Array.prototype.fill = Array.prototype.fill; }
     221                if (!Uint32Array.prototype.slice) { Uint32Array.prototype.slice = Array.prototype.slice; }
     222        }
     223        if (typeof Float32Array !== 'undefined') {
     224                if (!Float32Array.prototype.fill) { Float32Array.prototype.fill = Array.prototype.fill; }
     225                if (!Float32Array.prototype.slice) { Float32Array.prototype.slice = Array.prototype.slice; }
     226        }
     227        if (typeof Float64Array !== 'undefined') {
     228                if (!Float64Array.prototype.fill) { Float64Array.prototype.fill = Array.prototype.fill; }
     229                if (!Float64Array.prototype.slice) { Float64Array.prototype.slice = Array.prototype.slice; }
     230        }
     231        if (typeof TypedArray !== 'undefined') {
     232                if (!TypedArray.prototype.fill) { TypedArray.prototype.fill = Array.prototype.fill; }
     233                if (!TypedArray.prototype.slice) { TypedArray.prototype.slice = Array.prototype.slice; }
     234        }
     235} //}}}
     236function raycast(canvas, origin, ray) { //{{{
     237        var mesh = canvas.unitNode.mesh;
     238        if (!mesh || mesh.ready == false) { return; }
     239        if (!mesh.octree) { mesh.octree = new GL.Octree(mesh); }
     240       
     241        var hit = mesh.octree.testRay(origin, ray, 1e3, 1e10);
     242       
     243        if(!hit) { return; }
     244       
     245        hit.modelPos = vec3.copy(vec3.create(), hit.pos);
     246        vec3.transformMat4(hit.pos, hit.pos, canvas.unitNode.modelMatrix);
     247        vec3.transformMat4(hit.normal, hit.normal, canvas.unitNode.modelMatrix);
     248
     249        return hit;
    323250} //}}}
    324251//}}}
    325252//{{{ Interface Functions
     253function onTap(ev, canvas) { //{{{
     254        //Sets up a marker on a canvas that will track a point on the mesh. Can be dismissed by closing the display or clicking the marker.
     255        ev.preventDefault();
     256        if (!(canvas.dataMarkersAllowed && canvas.dataMarkersEnabled)) { return; }
     257        updateMarker(canvas, ev.srcEvent.layerX, ev.srcEvent.layerY, true);
     258} //}}}
     259function updateMarker(canvas, x, y, reset, origin, far) { //{{{
     260        //Can be called by onTap to create/reuse a marker, or by the marker's update function. Origin and far are optional and only used by the update function for recreating the raycast.
     261        if (!canvas.unitNode) { return; }
     262       
     263        var inverseMVPMatrix = mat4.invert(mat4.create(), mat4.multiply(mat4.create(), canvas.cameraMatrix, canvas.unitNode.modelMatrix));
     264        var origin = origin || vec3.transformMat4(vec3.create(), [(x - canvas.width / 2) / (canvas.width / 2), (canvas.height / 2 - y) / (canvas.height / 2), 0], inverseMVPMatrix);
     265        var far = far || vec3.transformMat4(vec3.create(), [(x - canvas.width / 2) / (canvas.width / 2), (canvas.height / 2 - y) / (canvas.height / 2), 1.0], inverseMVPMatrix);
     266        var ray = vec3.subtract(vec3.create(), far, origin);
     267        var hit = raycast(canvas, origin, ray);
     268       
     269        if (hit) {
     270                var coords = canvas.unitNode.mesh.vertexBuffers.coords.data;
     271                var latitude = md.mesh.lat;
     272                var longitude = md.mesh.long;
     273                var thickness;
     274                var velocity;
     275                if (md.results[0]) {
     276                        thickness = md.results[canvas.movieFrame].Thickness;
     277                        velocity = md.results[canvas.movieFrame].Vel;
     278                }
     279                else {
     280                        thickness = md.geometry.thickness;
     281                        velocity = md.initialization.vel;
     282                }
     283               
     284                var hitCoords = [coords[hit.indices[0]*2], coords[hit.indices[0]*2+1],coords[hit.indices[1]*2], coords[hit.indices[1]*2+1],coords[hit.indices[2]*2], coords[hit.indices[2]*2+1]];
     285                var hitLatitude = [latitude[hit.indices[0]], latitude[hit.indices[1]], latitude[hit.indices[2]]];
     286                var hitLongitude = [longitude[hit.indices[0]], longitude[hit.indices[1]], longitude[hit.indices[2]]];
     287                var hitThickness = [thickness[hit.indices[0]], thickness[hit.indices[1]], thickness[hit.indices[2]]];
     288                var hitVelocity = [velocity[hit.indices[0]], velocity[hit.indices[1]], velocity[hit.indices[2]]];
     289                var u = hitCoords[0] * hit.uvw[0] + hitCoords[2] * hit.uvw[1] + hitCoords[4] * hit.uvw[2];
     290                var v = hitCoords[1] * hit.uvw[0] + hitCoords[3] * hit.uvw[1] + hitCoords[5] * hit.uvw[2];
     291                var value = canvas.unitNode.caxis[0] * (1.0 - v) + canvas.unitNode.caxis[1] * v;
     292                var valueLatitude = Math.abs(hitLatitude[0] * hit.uvw[0] + hitLatitude[1] * hit.uvw[1] + hitLatitude[2] * hit.uvw[2]);
     293                var valueLongitude = Math.abs(hitLongitude[0] * hit.uvw[0] + hitLongitude[1] * hit.uvw[1] + hitLongitude[2] * hit.uvw[2]);
     294                var valueThickness = hitThickness[0] * hit.uvw[0] + hitThickness[1] * hit.uvw[1] + hitThickness[2] * hit.uvw[2];
     295                var valueVelocity = hitVelocity[0] * hit.uvw[0] + hitVelocity[1] * hit.uvw[1] + hitVelocity[2] * hit.uvw[2];
     296               
     297                var dataMarkerSize = canvas.dataMarkerSize;
     298                if (!canvas.marker) {
     299                        $('#' + canvas.id).after( '<img src=' + canvas.dataMarkerImage + ' alt="data marker" width="' + dataMarkerSize[0] + '" height="' + dataMarkerSize[1] + '" id="sim-data-marker-' + canvas.id + '" class="sim-data-marker noselect tooltip" data-tooltip-content="#tooltip-content-data-marker-' + canvas.id + '"></img><span id="tooltip-content-data-marker-' + canvas.id + '"></span>');
     300                        $('#sim-data-marker-' + canvas.id).css({
     301                                'position': 'absolute',
     302                                'left': (Math.round(x) - dataMarkerSize[0] / 2) + 'px',
     303                                'top': (Math.round(y) - dataMarkerSize[1]) + 'px',
     304                                'width': dataMarkerSize[0] + 'px',
     305                                'height': dataMarkerSize[1] + 'px',
     306                                'pointer-events': 'all',
     307                                'cursor': 'pointer',
     308                                'display': 'none'               
     309                        });
     310                        $('#sim-data-marker-' + canvas.id).tooltipster({
     311                                maxWidth: 320,
     312                                zIndex: 2200,
     313                                trigger: 'custom',
     314                                triggerOpen: {
     315                                        mouseenter: false,
     316                                        originClick: true,
     317                                        touchstart: false
     318                                },
     319                                triggerClose: {
     320                                        mouseleave: false,
     321                                        originClick: true,
     322                                        touchleave: false
     323                                }
     324                        });
     325                        canvas.marker = $('#sim-data-marker-' + canvas.id);
     326                        canvas.marker.on('click touch', function () {
     327                                canvas.marker.fadeOut(175);
     328                                canvas.dataMarkerDisplay.tooltipster('close');
     329                        });
     330                        canvas.marker.fadeIn(175);
     331                }
     332               
     333               
     334                canvas.marker.hit = hit;
     335                canvas.marker.update = function() {
     336                        if (!canvas.unitNode) { return; }
     337                        var screenPoint = vec3.transformMat4(vec3.create(), canvas.marker.hit.pos, canvas.cameraMatrix);
     338                        var x = screenPoint[0] * (canvas.width / 2) + canvas.width / 2;
     339                        var y = -screenPoint[1] * (canvas.height / 2) + canvas.height / 2;
     340                        updateMarker(canvas, Math.round(x), Math.round(y), false, origin, far);
     341                        canvas.marker.css({
     342                                'left': (Math.round(x) - dataMarkerSize[0] / 2) + 'px',
     343                                'top': (Math.round(y) - dataMarkerSize[1]) + 'px'
     344                        });
     345                        if (canvas.dataMarkerDisplay.tooltipster('status').state != 'closed') { canvas.dataMarkerDisplay.tooltipster('reposition'); }
     346                };
     347               
     348                if (!canvas.dataMarkerDisplay) {
     349                        canvas.dataMarkerDisplay = $('#sim-data-marker-' + canvas.id);
     350                        canvas.dataMarkerDisplay.tooltipster('open');
     351                }
     352                if (canvas.dataMarkerOptions) {
     353                        var format = [canvas.dataMarkerOptions.format[0]];     
     354                        for (var i = 1; i < canvas.dataMarkerOptions.format.length; i++) {
     355                                var formatString = canvas.dataMarkerOptions.format[i];
     356                                if (formatString.toLowerCase() == 'x') { format.push(hit.modelPos[0]); }
     357                                else if (formatString.toLowerCase() == 'y') { format.push(hit.modelPos[1]); }
     358                                else if (formatString.toLowerCase() == 'z') { format.push(hit.modelPos[2]); }
     359                                else if (formatString.toLowerCase() == 'lat') { format.push(valueLatitude); }
     360                                else if (formatString.toLowerCase() == 'long') { format.push(valueLongitude); }
     361                                else if (formatString.toLowerCase() == 'thickness') { format.push(valueThickness); }
     362                                else if (formatString.toLowerCase() == 'vel') { format.push(valueVelocity); }
     363                                else if (formatString.toLowerCase() == 'value') { format.push(value); }
     364                                else {format.push(formatString); }
     365                        }
     366                       
     367                        $('#tooltip-content-data-marker-' + canvas.id).html(sprintf.apply(null, format));
     368                        $('#tooltip-content-data-marker-' + canvas.id).css({
     369                                'font': canvas.dataMarkerOptions.font
     370                        });
     371                }
     372
     373                if (reset) { modifyDataMarkersEnabled(true,canvas); }
     374        }
     375} //}}}
    326376function onPan(ev,canvas,displaylog) { //{{{
    327377        ev.preventDefault();
     
    331381        }
    332382        if (ev.srcEvent.shiftKey || ev.pointers.length == 2) {
    333                 var deltaX = (canvas.lastDeltaX - ev.deltaX) / canvas.clientWidth / canvas.zoomFactor * -2;
    334                 var deltaY = (canvas.lastDeltaY - ev.deltaY) / canvas.clientHeight / canvas.zoomFactor * -2;
    335                
    336                 canvas.translation[0] += Math.cos(radians(canvas.rotation[0])) * deltaX - Math.sin(radians(canvas.rotation[0])) * deltaY;
    337                 canvas.translation[1] += Math.sin(radians(canvas.rotation[0])) * deltaX + Math.cos(radians(canvas.rotation[0])) * deltaY;
     383                if (!canvas.viewPanning) return;
     384                var deltaX = (canvas.lastDeltaX - ev.deltaX) / canvas.clientWidth / canvas.zoom * 2 * canvas.controlSensitivity * 6.371e6;
     385                var deltaY = (canvas.lastDeltaY - ev.deltaY) / canvas.clientHeight / canvas.zoom * 2 * canvas.controlSensitivity * 6.371e6;
     386               
     387                if (canvas.twod) {
     388                        canvas.translation[0] += Math.cos(DEG2RAD * canvas.rotation[0]) * deltaX - Math.sin(DEG2RAD * 0) * deltaY;
     389                        canvas.translation[2] += Math.sin(DEG2RAD * canvas.rotation[0]) * deltaX + Math.cos(DEG2RAD * 0) * deltaY;
     390                }
     391                else {
     392                        canvas.translation[0] += Math.cos(DEG2RAD * canvas.rotation[0]) * deltaX - Math.sin(DEG2RAD * canvas.rotation[0]) * deltaY;
     393                        canvas.translation[2] += Math.sin(DEG2RAD * canvas.rotation[0]) * deltaX + Math.cos(DEG2RAD * canvas.rotation[0]) * deltaY;
     394                }
    338395        }
    339396        else {
    340                 canvas.rotation[0] += degrees((canvas.lastDeltaX - ev.deltaX) / canvas.clientWidth / canvas.zoomFactor * -2);
    341                 canvas.rotation[1] += degrees((canvas.lastDeltaY - ev.deltaY) / canvas.clientHeight / canvas.zoomFactor * -2);
    342                
    343                 if (canvas.rotation[0] > 360) {canvas.rotation[0] -= 360};
    344                 if (canvas.rotation[0] < -360) {canvas.rotation[0] += 360};
    345                 if (canvas.rotation[1] > 180) {canvas.rotation[1] -= 360};
    346                 if (canvas.rotation[1] < -180) {canvas.rotation[1] += 360};
     397                canvas.rotation[0] += (canvas.lastDeltaX - ev.deltaX) / canvas.clientWidth * -2 * canvas.controlSensitivity * RAD2DEG;
     398                canvas.rotation[1] += (canvas.lastDeltaY - ev.deltaY) / canvas.clientHeight * -2 * canvas.controlSensitivity * RAD2DEG;
     399               
     400                if (canvas.rotation[0] > 360) { canvas.rotation[0] -= 360; };
     401                if (canvas.rotation[0] < -360) { canvas.rotation[0] += 360; };
     402                if (canvas.rotation[1] > 180) { canvas.rotation[1] -= 360; };
     403                if (canvas.rotation[1] < -180) { canvas.rotation[1] += 360; };
    347404               
    348405                canvas.rotation[0] = clamp(canvas.rotation[0], canvas.rotationAzimuthBounds[0], canvas.rotationAzimuthBounds[1]);
     
    351408        canvas.lastDeltaX = ev.deltaX;
    352409        canvas.lastDeltaY = ev.deltaY;
    353        
    354         if (displaylog) console.log(canvas.rotation);
     410
     411        if (displaylog) { console.log(canvas.rotation); }
    355412} //}}}
    356413function onPinch(ev,canvas,displaylog) { //{{{
    357414        ev.preventDefault();
    358         if (ev.type == 'pinchstart') {
    359                 canvas.zoomLast = canvas.zoomFactor;
    360         }
    361         else {
    362                 canvas.zoomFactor = clamp(ev.scale * canvas.zoomLast, canvas.zoomBounds[0], canvas.zoomBounds[1]);
    363                 if (displaylog) console.log(canvas.zoomFactor);
    364         }
     415        if (ev.type == 'pinchstart') { canvas.zoomLast = canvas.zoom; }
     416        else { modifyZoom(ev.scale * canvas.zoomLast, canvas, displaylog); }
    365417} //}}}
    366418function onZoom(ev,canvas,displaylog) { //{{{
    367419        ev.preventDefault();
    368         var delta = clamp(clamp(ev.scale || ev.wheelDelta || -ev.detail, -1, 1) / (1000  * canvas.zoomFactor), -0.1, 0.1);
    369         canvas.zoomFactor = clamp(canvas.zoomFactor + delta, canvas.zoomBounds[0], canvas.zoomBounds[1]);
    370        
    371         if (displaylog) console.log(canvas.zoomFactor);
     420        var delta = clamp(ev.scale || ev.wheelDelta || -ev.detail, -1, 1) * canvas.controlSensitivity * canvas.zoom / 20;
     421        modifyZoom(canvas.zoom + delta, canvas, displaylog);
     422} //}}}
     423function modifyZoom(value,canvas,displaylog) { //{{{
     424        canvas.zoom = clamp(value, canvas.zoomBounds[0], canvas.zoomBounds[1]);
     425        if (displaylog) { console.log(canvas.zoom); }
     426} //}}}
     427function modifyDataMarkersEnabled(value,canvas) { //{{{
     428        canvas.dataMarkersEnabled = value;
     429        if (!canvas.dataMarkersEnabled && canvas.marker) {
     430                canvas.marker.fadeOut(175);
     431                canvas.dataMarkerDisplay.tooltipster('close');
     432        }
     433        else if (canvas.dataMarkersEnabled && canvas.marker) {
     434                canvas.marker.fadeIn(175);
     435                canvas.dataMarkerDisplay.tooltipster('open');
     436        }
     437} //}}}
     438function toggleMoviePlay(canvas) { //{{{
     439        canvas.moviePlay = !canvas.moviePlay;
     440        if (canvas.moviePlay){
     441                canvas.playButton.find("span").removeClass("fa-play");
     442                canvas.playButton.find("span").addClass("fa-pause");
     443        }
     444        else{
     445                canvas.playButton.find("span").removeClass("fa-pause");
     446                canvas.playButton.find("span").addClass("fa-play");
     447        }
    372448} //}}}
    373449//}}}
     
    381457        var azimuthRotationMatrix = mat4.create();
    382458        var elevationRotationMatrix = mat4.create();
    383 
     459        var aspectRatio = canvas.clientWidth / canvas.clientHeight;
     460        var cameraPosition = vec3.create();
     461
     462        if (canvas.twod) { mat4.ortho(pMatrix, -aspectRatio*6.371e6/canvas.zoom, aspectRatio*6.371e6/canvas.zoom, -6.371e6/canvas.zoom, 6.371e6/canvas.zoom, -1.0, 1e10); }
     463        else { mat4.perspective(pMatrix, 45 * DEG2RAD, aspectRatio, 1e3, 1e10); }
     464       
     465        //Apply worldspace translation
     466        mat4.translate(translateMatrix, translateMatrix, [-canvas.translation[0],-canvas.translation[1],-canvas.translation[2]]);
     467        mat4.multiply(vMatrix, translateMatrix, vMatrix);
     468       
     469        //Calculate rotation around camera focal point about worldspace origin
    384470        if (canvas.twod) {
    385                 mat4.ortho(pMatrix, -1/canvas.zoomFactor, 1/canvas.zoomFactor, -1/canvas.zoomFactor, 1/canvas.zoomFactor, -1.0, 10000.0);
     471                mat4.rotate(azimuthRotationMatrix, azimuthRotationMatrix, DEG2RAD * 0, [0, 1, 0]);
     472                mat4.rotate(elevationRotationMatrix, elevationRotationMatrix, DEG2RAD * 90, [1, 0, 0]);
     473                mat4.multiply(rotationMatrix, elevationRotationMatrix, azimuthRotationMatrix);
    386474        }
    387475        else {
    388                 mat4.perspective(pMatrix, 90 * Math.PI / 180, canvas.clientWidth / canvas.clientHeight, 0.001, 10000.0);
    389         }
    390        
    391         //Apply worldspace translation
    392         mat4.translate(translateMatrix, translateMatrix, [canvas.translation[0], canvas.translation[2], canvas.translation[1]]);
    393         mat4.multiply(vMatrix, translateMatrix, vMatrix);
    394        
    395         //Calculate rotation around camera focal point about worldspace origin
    396         mat4.rotate(azimuthRotationMatrix, azimuthRotationMatrix, radians(canvas.rotation[0]), [0, 1, 0]);
    397         mat4.rotate(elevationRotationMatrix, elevationRotationMatrix, radians(canvas.rotation[1]), [1, 0, 0]);
    398         mat4.multiply(rotationMatrix, elevationRotationMatrix, azimuthRotationMatrix);
    399        
    400         //Apply rotation and scaling transform
     476                mat4.rotate(azimuthRotationMatrix, azimuthRotationMatrix, DEG2RAD * canvas.rotation[0], [0, 1, 0]);
     477                mat4.rotate(elevationRotationMatrix, elevationRotationMatrix, DEG2RAD * canvas.rotation[1], [1, 0, 0]);
     478                mat4.multiply(rotationMatrix, elevationRotationMatrix, azimuthRotationMatrix);
     479        }
     480       
     481        //Apply rotation transform
    401482        mat4.multiply(vMatrix, rotationMatrix, vMatrix);
    402        
     483
    403484        //Apply screenspace translation
    404485        mat4.identity(translateMatrix);
    405         mat4.translate(translateMatrix, translateMatrix, [0.0, 0.0, -1/canvas.zoomFactor]);
     486        mat4.translate(translateMatrix, translateMatrix, [0.0, 0.0, -6.371e6/canvas.zoom]);
    406487        mat4.multiply(vMatrix, translateMatrix, vMatrix);
    407 
     488       
     489        //Calculate fields for lighting and raycasts
     490        mat4.invert(canvas.vInverseMatrix, vMatrix);
     491       
    408492        //Apply projection matrix to get camera matrix
    409493        mat4.multiply(canvas.cameraMatrix, pMatrix, vMatrix);
    410 //      canvas.cameraMatrix = mat4.create();
     494        mat4.invert(canvas.inverseCameraMatrix, canvas.cameraMatrix);
     495        vec3.transformMat4(canvas.cameraPosition, cameraPosition, canvas.inverseCameraMatrix);
    411496}//}}}
    412 function drawSceneGraphNode(gl,canvas,node) { //{{{
    413         if (!node["enabled"]) {
     497function drawSceneGraphNode(canvas,node) { //{{{
     498        if (!node.enabled) { return; }
     499
     500        var gl = canvas.gl;
     501        gl.makeCurrent();
     502       
     503        var mvpMatrix = mat4.create();
     504        mat4.multiply(mvpMatrix, canvas.cameraMatrix, node.modelMatrix);
     505       
     506        if (node.texture) { node.texture.bind(0); }
     507        if (node.disableDepthTest) { gl.disable(gl.DEPTH_TEST); }
     508        if (node.enableCullFace) { gl.enable(gl.CULL_FACE); }
     509
     510        gl.cullFace(node.cullFace);
     511        gl.lineWidth(node.lineWidth);
     512        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
     513
     514        //Setup for light that originates from camera
     515        var origin = vec3.fromValues(0, 0, 0);
     516        var lightOrigin = vec3.fromValues(0, 0, 0);
     517        var cameraPositionRelative = vec3.create();
     518        vec3.transformMat4(origin, origin, canvas.vInverseMatrix);
     519        vec3.normalize(lightOrigin, lightOrigin);
     520        vec3.sub(cameraPositionRelative, origin, node.translation);
     521        cameraHeight = vec3.length(cameraPositionRelative);
     522       
     523        var atm = {                                                     //Default Values
     524                                wavelength_r: 0.65,             //0.65          Red wavelength (micrometers)
     525                                wavelength_g: 0.57,                     //0.57          Green wavelength (micrometers)
     526                                wavelength_b: 0.475,            //0.475         Green wavelength (micrometers)
     527                                eSun: 100.0,                            //20.0          Sun intensity   
     528                                kRayleigh: 0.0025,                      //0.0025        Rayleigh scattering amount
     529                                kMie: 0.000,                            //0.01          Mie scattering amount
     530                                g: -0.99,                                       //-0.99         Mie phase asymmetry/direction factor
     531                                hdr_exposure: 0.8,                      //0.8           High Dynamic Range Exposure
     532                                scale: 1.25,                            //1.025         Scale of atmosphere. WARNING: Change atmosphereScale in applyoptions.js, and scaling constants.
     533                                scaleDepth: 0.25,                       //0.25          Percentage altitude at which the atmosphere's average density is found
     534                                a: -0.00287,                            //-0.00287      Scaling constant a
     535                                b: 0.459,                                       //0.459         Scaling constant b
     536                                c: 3.83,                                        //3.83          Scaling constant c
     537                                d: -6.80,                                       //-6.80         Scaling constant d
     538                                e: 3.6,                                         //5.25          Scaling constant e. Lower when increasing atmosphere scale.
     539                                attenuation: 0.5                        //0.5           Strength of atmospheric scattering on ground shading.
     540        };
     541                       
     542        var inv_wavelength4 = [1.0 / Math.pow(atm.wavelength_r, 4), 1.0 / Math.pow(atm.wavelength_g, 4), 1.0 / Math.pow(atm.wavelength_b, 4)];
     543        var innerRadius = 6.371e6;
     544        var outerRadius = innerRadius*atm.scale;
     545        var scale = 1.0 / (outerRadius - innerRadius);
     546        var scaleDepth = atm.scaleDepth;
     547       
     548        node.shader.uniforms({
     549                m4MVP: mvpMatrix,
     550                m4Model: node.modelMatrix,
     551                u_texture: 0,
     552                u_alpha: node.alpha,
     553                u_maskEnabled: node.maskEnabled,
     554                u_maskHeight: node.maskHeight,
     555                u_maskColor: node.maskColor,
     556                v3CameraPosition: origin,
     557                v3Translate: node.translation,
     558                v3LightPos: lightOrigin,
     559                v3InvWavelength: inv_wavelength4,
     560                fOuterRadius: outerRadius,
     561                fOuterRadius2: outerRadius * outerRadius,
     562                fInnerRadius: innerRadius,
     563                fInnerRadius2: innerRadius * innerRadius,
     564                fKrESun: atm.kRayleigh * atm.eSun,
     565                fKmESun: atm.kMie * atm.eSun,
     566                fKr4PI: atm.kRayleigh * 4 * Math.PI,
     567                fKm4PI: atm.kMie * 4 * Math.PI,
     568                fScale: scale,
     569                fScaleDepth: scaleDepth,
     570                fScaleOverScaleDepth: scale/scaleDepth,
     571                v3LightPosFrag: lightOrigin,
     572                fHdrExposure: atm.hdr_exposure,
     573                g: atm.g,                       
     574                g2: atm.g * atm.g,
     575                a: atm.a,
     576                b: atm.b,
     577                c: atm.c,
     578                d: atm.d,               
     579                e: atm.e,
     580                attenuation: atm.attenuation
     581        }).draw(node.mesh, node.drawMode, 'triangles');
     582       
     583        gl.enable(gl.DEPTH_TEST);
     584        gl.disable(gl.CULL_FACE);
     585} //}}}
     586function draw(canvas,options) { //{{{
     587        if (canvas.textcanvas) { canvas.textcanvas.draw(canvas); }
     588
     589        var nodes = canvas.nodes;
     590        if (nodes.length < 1) {
     591                canvas.drawHandler = window.requestAnimationFrame(function(time) { draw(canvas,options); });
    414592                return;
    415593        }
    416         if (node["texture"]) {
    417                 if (!node["texture"]["isLoaded"]) {
     594        for (var node in nodes) {       
     595                if ((nodes[node].texture && nodes[node].texture.ready == false) || nodes[node].shader.ready == false || nodes[node].mesh.ready == false) {
     596                        canvas.drawHandler = window.requestAnimationFrame(function(time) { draw(canvas,options); });
    418597                        return;
    419598                }
    420599        }
    421         bindAttributes(gl, node["shader"], node["buffers"]);
    422         var mvpMatrix = mat4.create();
    423         mat4.multiply(mvpMatrix, canvas.cameraMatrix, node["modelMatrix"]);
    424         gl.uniformMatrix4fv(node["shader"]["uMVPMatrix"], false, mvpMatrix);
    425         gl.uniform1f(node["shader"]["uAlpha"], node["alpha"]);
    426         gl.uniform1i(node["shader"]["uMaskEnabled"], node["maskEnabled"]);
    427         gl.uniform1f(node["shader"]["uMaskHeight"], node["maskHeight"]);
    428         gl.uniform4fv(node["shader"]["uMaskColor"], node["maskColor"]);
    429         if (node["texture"]) {
    430                 gl.activeTexture(gl.TEXTURE0);
    431                 gl.bindTexture(gl.TEXTURE_2D, node["texture"]);
    432                 gl.uniform1i(node["shader"]["uColorSampler"], 0);       
    433         }
    434         if (node["disableDepthTest"]) {
    435                 gl.disable(gl.DEPTH_TEST);
    436         }
    437         gl.blendFunc (gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    438         if  (node["useIndexBuffer"] == true) {
    439                 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, node["buffers"][node["buffers"].length - 1]);
    440                 gl.drawElements(node["drawMode"], node["buffers"][node["buffers"].length - 1].numItems, gl.UNSIGNED_SHORT, 0);
    441         }
    442         else {
    443                 gl.drawArrays(node["drawMode"], 0, node["buffers"][0].numItems);
    444         }       
    445         gl.enable(gl.DEPTH_TEST);
    446 } //}}}
    447 function bindAttributes(gl,shaderProgram,bufferArray) { //{{{
    448         gl.useProgram(shaderProgram);
    449         var arrayNumber = 0;
    450         for (var propertyName in shaderProgram) {
    451                 if (propertyName[0] == "a") {
    452                         if (bufferArray[arrayNumber].itemSize > 1) {
    453                                 gl.bindBuffer(gl.ARRAY_BUFFER, bufferArray[arrayNumber]);
    454                                 gl.vertexAttribPointer(shaderProgram[propertyName], bufferArray[arrayNumber].itemSize, gl.FLOAT, false, 0, 0);
    455                                 arrayNumber++;
    456                         }
    457                 }
    458         }
    459 } //}}}
    460 function draw(gl,options,canvas,nodes) { //{{{
    461         // Ensure canvas and gl viewport sizes are the same
    462         var displayWidth  = canvas.clientWidth;
    463         var displayHeight = canvas.clientHeight;
    464         if (canvas.width  != displayWidth || canvas.height != displayHeight) {
    465                 canvas.width  = displayWidth;
    466                 canvas.height = displayHeight;
    467                 gl.viewport(0, 0, canvas.width, canvas.height);
    468         }
    469        
    470         if (canvas.textcanvas) {
    471                 canvas.textcanvas.draw(canvas);
    472         }
    473 
    474         // Set clear color to black, fully opaque
    475         var backgroundcolor=new RGBColor(options.getfieldvalue('backgroundcolor','lightcyan'));
    476         if(backgroundcolor.ok){
    477                 gl.clearColor(backgroundcolor.r/255.0, backgroundcolor.g/255.0, backgroundcolor.b/255.0, 1.0);
    478         }
    479         else throw Error(sprintf("s%s%s\n","initWebGL error message: cound not find out background color for curent canvas ",canvas));
    480                
    481         // Skip drawing of new frame if any texture is not yet loaded
    482         var nodes = canvas.nodes;
    483         for (var node in nodes) {
    484                 if (nodes[node]["texture"] && !nodes[node]["texture"]["isLoaded"]) {
    485                         window.requestAnimationFrame(function(time) {draw(gl,options,canvas,nodes)});
    486                         return;
    487                 }
    488         }
    489         // Else, clear the color as well as the depth buffer for new frame
     600       
     601        var rect = canvas.getBoundingClientRect();
     602        canvas.width  = rect.width;
     603        canvas.height = rect.height;
     604       
     605        var gl = canvas.gl;
     606        gl.makeCurrent(); //litegl function to handle switching between multiple canvases
     607        gl.viewport(0, 0, rect.width, rect.height);
     608        gl.clearColor(canvas.backgroundcolor[0], canvas.backgroundcolor[1], canvas.backgroundcolor[2], canvas.backgroundcolor[3]);
    490609        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    491 
    492         window.requestAnimationFrame(function(time) {draw(gl,options,canvas,nodes)});
    493610       
    494611        updateCameraMatrix(canvas);
    495612       
    496         var drawPassNumber = 2;
     613        if (canvas.marker) { canvas.marker.update(); }
     614       
     615        var drawPassNumber = 3;
    497616        for (var i = drawPassNumber - 1; i >= 0; i--) {
    498617                for (var node in nodes) {
    499                         if (nodes[node]["drawOrder"] == i) {
    500                                 drawSceneGraphNode(gl, canvas, nodes[node]);
    501                         }
    502                 }
    503         }
     618                        if (nodes[node].drawOrder == i) { drawSceneGraphNode(canvas,nodes[node]); }
     619                }
     620        }
     621
     622        canvas.drawHandler = window.requestAnimationFrame(function(time) { draw(canvas,options); });
    504623} //}}}
    505624//}}}
Note: See TracChangeset for help on using the changeset viewer.