Home | History | Annotate | Download | only in resources
      1 /*
      2  * Copyright (C) 2009 Apple Inc. All Rights Reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 //
     27 // initWebGL
     28 //
     29 // Initialize the Canvas element with the passed name as a WebGL object and return the
     30 // WebGLRenderingContext.
     31 function initWebGL(canvasName, vshader, fshader, attribs, clearColor, clearDepth)
     32 {
     33     var canvas = document.getElementById(canvasName);
     34     return gl = WebGLUtils.setupWebGL(canvas);
     35 }
     36 
     37 function log(msg) {
     38     if (window.console && window.console.log) {
     39         window.console.log(msg);
     40     }
     41 }
     42 
     43 // Load shaders with the passed names and create a program with them. Return this program
     44 // in the 'program' property of the returned context.
     45 //
     46 // For each string in the passed attribs array, bind an attrib with that name at that index.
     47 // Once the attribs are bound, link the program and then use it.
     48 //
     49 // Set the clear color to the passed array (4 values) and set the clear depth to the passed value.
     50 // Enable depth testing and blending with a blend func of (SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
     51 //
     52 // A console function is added to the context: console(string). This can be replaced
     53 // by the caller. By default, it maps to the window.console() function on WebKit and to
     54 // an empty function on other browsers.
     55 //
     56 function simpleSetup(gl, vshader, fshader, attribs, clearColor, clearDepth)
     57 {
     58     // create our shaders
     59     var vertexShader = loadShader(gl, vshader);
     60     var fragmentShader = loadShader(gl, fshader);
     61 
     62     // Create the program object
     63     var program = gl.createProgram();
     64 
     65     // Attach our two shaders to the program
     66     gl.attachShader (program, vertexShader);
     67     gl.attachShader (program, fragmentShader);
     68 
     69     // Bind attributes
     70     for (var i = 0; i < attribs.length; ++i)
     71         gl.bindAttribLocation (program, i, attribs[i]);
     72 
     73     // Link the program
     74     gl.linkProgram(program);
     75 
     76     // Check the link status
     77     var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
     78     if (!linked && !gl.isContextLost()) {
     79         // something went wrong with the link
     80         var error = gl.getProgramInfoLog (program);
     81         log("Error in program linking:"+error);
     82 
     83         gl.deleteProgram(program);
     84         gl.deleteProgram(fragmentShader);
     85         gl.deleteProgram(vertexShader);
     86 
     87         return null;
     88     }
     89 
     90     gl.useProgram(program);
     91 
     92     gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
     93     gl.clearDepth(clearDepth);
     94 
     95     gl.enable(gl.DEPTH_TEST);
     96     gl.enable(gl.BLEND);
     97     gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
     98 
     99     return program;
    100 }
    101 
    102 //
    103 // loadShader
    104 //
    105 // 'shaderId' is the id of a <script> element containing the shader source string.
    106 // Load this shader and return the WebGLShader object corresponding to it.
    107 //
    108 function loadShader(ctx, shaderId)
    109 {
    110     var shaderScript = document.getElementById(shaderId);
    111     if (!shaderScript) {
    112         log("*** Error: shader script '"+shaderId+"' not found");
    113         return null;
    114     }
    115 
    116     if (shaderScript.type == "x-shader/x-vertex")
    117         var shaderType = ctx.VERTEX_SHADER;
    118     else if (shaderScript.type == "x-shader/x-fragment")
    119         var shaderType = ctx.FRAGMENT_SHADER;
    120     else {
    121         log("*** Error: shader script '"+shaderId+"' of undefined type '"+shaderScript.type+"'");
    122         return null;
    123     }
    124 
    125     // Create the shader object
    126     var shader = ctx.createShader(shaderType);
    127 
    128     // Load the shader source
    129     ctx.shaderSource(shader, shaderScript.text);
    130 
    131     // Compile the shader
    132     ctx.compileShader(shader);
    133 
    134     // Check the compile status
    135     var compiled = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);
    136     if (!compiled && !ctx.isContextLost()) {
    137         // Something went wrong during compilation; get the error
    138         var error = ctx.getShaderInfoLog(shader);
    139         log("*** Error compiling shader '"+shaderId+"':"+error);
    140         ctx.deleteShader(shader);
    141         return null;
    142     }
    143 
    144     return shader;
    145 }
    146 
    147 //
    148 // makeBox
    149 //
    150 // Create a box with vertices, normals and texCoords. Create VBOs for each as well as the index array.
    151 // Return an object with the following properties:
    152 //
    153 //  normalObject        WebGLBuffer object for normals
    154 //  texCoordObject      WebGLBuffer object for texCoords
    155 //  vertexObject        WebGLBuffer object for vertices
    156 //  indexObject         WebGLBuffer object for indices
    157 //  numIndices          The number of indices in the indexObject
    158 //
    159 function makeBox(ctx)
    160 {
    161     // box
    162     //    v6----- v5
    163     //   /|      /|
    164     //  v1------v0|
    165     //  | |     | |
    166     //  | |v7---|-|v4
    167     //  |/      |/
    168     //  v2------v3
    169     //
    170     // vertex coords array
    171     var vertices = new Float32Array(
    172         [  1, 1, 1,  -1, 1, 1,  -1,-1, 1,   1,-1, 1,    // v0-v1-v2-v3 front
    173            1, 1, 1,   1,-1, 1,   1,-1,-1,   1, 1,-1,    // v0-v3-v4-v5 right
    174            1, 1, 1,   1, 1,-1,  -1, 1,-1,  -1, 1, 1,    // v0-v5-v6-v1 top
    175           -1, 1, 1,  -1, 1,-1,  -1,-1,-1,  -1,-1, 1,    // v1-v6-v7-v2 left
    176           -1,-1,-1,   1,-1,-1,   1,-1, 1,  -1,-1, 1,    // v7-v4-v3-v2 bottom
    177            1,-1,-1,  -1,-1,-1,  -1, 1,-1,   1, 1,-1 ]   // v4-v7-v6-v5 back
    178     );
    179 
    180     // normal array
    181     var normals = new Float32Array(
    182         [  0, 0, 1,   0, 0, 1,   0, 0, 1,   0, 0, 1,     // v0-v1-v2-v3 front
    183            1, 0, 0,   1, 0, 0,   1, 0, 0,   1, 0, 0,     // v0-v3-v4-v5 right
    184            0, 1, 0,   0, 1, 0,   0, 1, 0,   0, 1, 0,     // v0-v5-v6-v1 top
    185           -1, 0, 0,  -1, 0, 0,  -1, 0, 0,  -1, 0, 0,     // v1-v6-v7-v2 left
    186            0,-1, 0,   0,-1, 0,   0,-1, 0,   0,-1, 0,     // v7-v4-v3-v2 bottom
    187            0, 0,-1,   0, 0,-1,   0, 0,-1,   0, 0,-1 ]    // v4-v7-v6-v5 back
    188        );
    189 
    190 
    191     // texCoord array
    192     var texCoords = new Float32Array(
    193         [  1, 1,   0, 1,   0, 0,   1, 0,    // v0-v1-v2-v3 front
    194            0, 1,   0, 0,   1, 0,   1, 1,    // v0-v3-v4-v5 right
    195            1, 0,   1, 1,   0, 1,   0, 0,    // v0-v5-v6-v1 top
    196            1, 1,   0, 1,   0, 0,   1, 0,    // v1-v6-v7-v2 left
    197            0, 0,   1, 0,   1, 1,   0, 1,    // v7-v4-v3-v2 bottom
    198            0, 0,   1, 0,   1, 1,   0, 1 ]   // v4-v7-v6-v5 back
    199        );
    200 
    201     // index array
    202     var indices = new Uint8Array(
    203         [  0, 1, 2,   0, 2, 3,    // front
    204            4, 5, 6,   4, 6, 7,    // right
    205            8, 9,10,   8,10,11,    // top
    206           12,13,14,  12,14,15,    // left
    207           16,17,18,  16,18,19,    // bottom
    208           20,21,22,  20,22,23 ]   // back
    209       );
    210 
    211     var retval = { };
    212 
    213     retval.normalObject = ctx.createBuffer();
    214     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
    215     ctx.bufferData(ctx.ARRAY_BUFFER, normals, ctx.STATIC_DRAW);
    216 
    217     retval.texCoordObject = ctx.createBuffer();
    218     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject);
    219     ctx.bufferData(ctx.ARRAY_BUFFER, texCoords, ctx.STATIC_DRAW);
    220 
    221     retval.vertexObject = ctx.createBuffer();
    222     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
    223     ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW);
    224 
    225     ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
    226 
    227     retval.indexObject = ctx.createBuffer();
    228     ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject);
    229     ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, indices, ctx.STATIC_DRAW);
    230     ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
    231 
    232     retval.numIndices = indices.length;
    233 
    234     return retval;
    235 }
    236 
    237 //
    238 // makeSphere
    239 //
    240 // Create a sphere with the passed number of latitude and longitude bands and the passed radius.
    241 // Sphere has vertices, normals and texCoords. Create VBOs for each as well as the index array.
    242 // Return an object with the following properties:
    243 //
    244 //  normalObject        WebGLBuffer object for normals
    245 //  texCoordObject      WebGLBuffer object for texCoords
    246 //  vertexObject        WebGLBuffer object for vertices
    247 //  indexObject         WebGLBuffer object for indices
    248 //  numIndices          The number of indices in the indexObject
    249 //
    250 function makeSphere(ctx, radius, lats, longs)
    251 {
    252     var geometryData = [ ];
    253     var normalData = [ ];
    254     var texCoordData = [ ];
    255     var indexData = [ ];
    256 
    257     for (var latNumber = 0; latNumber <= lats; ++latNumber) {
    258         for (var longNumber = 0; longNumber <= longs; ++longNumber) {
    259             var theta = latNumber * Math.PI / lats;
    260             var phi = longNumber * 2 * Math.PI / longs;
    261             var sinTheta = Math.sin(theta);
    262             var sinPhi = Math.sin(phi);
    263             var cosTheta = Math.cos(theta);
    264             var cosPhi = Math.cos(phi);
    265 
    266             var x = cosPhi * sinTheta;
    267             var y = cosTheta;
    268             var z = sinPhi * sinTheta;
    269             var u = 1-(longNumber/longs);
    270             var v = latNumber/lats;
    271 
    272             normalData.push(x);
    273             normalData.push(y);
    274             normalData.push(z);
    275             texCoordData.push(u);
    276             texCoordData.push(v);
    277             geometryData.push(radius * x);
    278             geometryData.push(radius * y);
    279             geometryData.push(radius * z);
    280         }
    281     }
    282 
    283     for (var latNumber = 0; latNumber < lats; ++latNumber) {
    284         for (var longNumber = 0; longNumber < longs; ++longNumber) {
    285             var first = (latNumber * (longs+1)) + longNumber;
    286             var second = first + longs + 1;
    287             indexData.push(first);
    288             indexData.push(second);
    289             indexData.push(first+1);
    290 
    291             indexData.push(second);
    292             indexData.push(second+1);
    293             indexData.push(first+1);
    294         }
    295     }
    296 
    297     var retval = { };
    298 
    299     retval.normalObject = ctx.createBuffer();
    300     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
    301     ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(normalData), ctx.STATIC_DRAW);
    302 
    303     retval.texCoordObject = ctx.createBuffer();
    304     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject);
    305     ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(texCoordData), ctx.STATIC_DRAW);
    306 
    307     retval.vertexObject = ctx.createBuffer();
    308     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
    309     ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(geometryData), ctx.STATIC_DRAW);
    310 
    311     retval.numIndices = indexData.length;
    312     retval.indexObject = ctx.createBuffer();
    313     ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject);
    314     ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexData), ctx.STREAM_DRAW);
    315 
    316     return retval;
    317 }
    318 
    319 // Array of Objects curently loading
    320 var g_loadingObjects = [];
    321 
    322 // Clears all the Objects currently loading.
    323 // This is used to handle context lost events.
    324 function clearLoadingObjects() {
    325     for (var ii = 0; ii < g_loadingObjects.length; ++ii) {
    326         g_loadingObjects[ii].onreadystatechange = undefined;
    327     }
    328     g_loadingObjects = [];
    329 }
    330 
    331 //
    332 // loadObj
    333 //
    334 // Load a .obj file from the passed URL. Return an object with a 'loaded' property set to false.
    335 // When the object load is complete, the 'loaded' property becomes true and the following
    336 // properties are set:
    337 //
    338 //  normalObject        WebGLBuffer object for normals
    339 //  texCoordObject      WebGLBuffer object for texCoords
    340 //  vertexObject        WebGLBuffer object for vertices
    341 //  indexObject         WebGLBuffer object for indices
    342 //  numIndices          The number of indices in the indexObject
    343 //
    344 function loadObj(ctx, url)
    345 {
    346     var obj = { loaded : false };
    347     obj.ctx = ctx;
    348     var req = new XMLHttpRequest();
    349     req.obj = obj;
    350     g_loadingObjects.push(req);
    351     req.onreadystatechange = function () { processLoadObj(req) };
    352     req.open("GET", url, true);
    353     req.send(null);
    354     return obj;
    355 }
    356 
    357 function processLoadObj(req)
    358 {
    359     log("req="+req)
    360     // only if req shows "complete"
    361     if (req.readyState == 4) {
    362         g_loadingObjects.splice(g_loadingObjects.indexOf(req), 1);
    363         doLoadObj(req.obj, req.responseText);
    364     }
    365 }
    366 
    367 function doLoadObj(obj, text)
    368 {
    369     vertexArray = [ ];
    370     normalArray = [ ];
    371     textureArray = [ ];
    372     indexArray = [ ];
    373 
    374     var vertex = [ ];
    375     var normal = [ ];
    376     var texture = [ ];
    377     var facemap = { };
    378     var index = 0;
    379 
    380     // This is a map which associates a range of indices with a name
    381     // The name comes from the 'g' tag (of the form "g NAME"). Indices
    382     // are part of one group until another 'g' tag is seen. If any indices
    383     // come before a 'g' tag, it is given the group name "_unnamed"
    384     // 'group' is an object whose property names are the group name and
    385     // whose value is a 2 element array with [<first index>, <num indices>]
    386     var groups = { };
    387     var currentGroup = [-1, 0];
    388     groups["_unnamed"] = currentGroup;
    389 
    390     var lines = text.split("\n");
    391     for (var lineIndex in lines) {
    392         var line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, "");
    393 
    394         // ignore comments
    395         if (line[0] == "#")
    396             continue;
    397 
    398         var array = line.split(" ");
    399         if (array[0] == "g") {
    400             // new group
    401             currentGroup = [indexArray.length, 0];
    402             groups[array[1]] = currentGroup;
    403         }
    404         else if (array[0] == "v") {
    405             // vertex
    406             vertex.push(parseFloat(array[1]));
    407             vertex.push(parseFloat(array[2]));
    408             vertex.push(parseFloat(array[3]));
    409         }
    410         else if (array[0] == "vt") {
    411             // normal
    412             texture.push(parseFloat(array[1]));
    413             texture.push(parseFloat(array[2]));
    414         }
    415         else if (array[0] == "vn") {
    416             // normal
    417             normal.push(parseFloat(array[1]));
    418             normal.push(parseFloat(array[2]));
    419             normal.push(parseFloat(array[3]));
    420         }
    421         else if (array[0] == "f") {
    422             // face
    423             if (array.length != 4) {
    424                 log("*** Error: face '"+line+"' not handled");
    425                 continue;
    426             }
    427 
    428             for (var i = 1; i < 4; ++i) {
    429                 if (!(array[i] in facemap)) {
    430                     // add a new entry to the map and arrays
    431                     var f = array[i].split("/");
    432                     var vtx, nor, tex;
    433 
    434                     if (f.length == 1) {
    435                         vtx = parseInt(f[0]) - 1;
    436                         nor = vtx;
    437                         tex = vtx;
    438                     }
    439                     else if (f.length = 3) {
    440                         vtx = parseInt(f[0]) - 1;
    441                         tex = parseInt(f[1]) - 1;
    442                         nor = parseInt(f[2]) - 1;
    443                     }
    444                     else {
    445                         obj.ctx.console.log("*** Error: did not understand face '"+array[i]+"'");
    446                         return null;
    447                     }
    448 
    449                     // do the vertices
    450                     var x = 0;
    451                     var y = 0;
    452                     var z = 0;
    453                     if (vtx * 3 + 2 < vertex.length) {
    454                         x = vertex[vtx*3];
    455                         y = vertex[vtx*3+1];
    456                         z = vertex[vtx*3+2];
    457                     }
    458                     vertexArray.push(x);
    459                     vertexArray.push(y);
    460                     vertexArray.push(z);
    461 
    462                     // do the textures
    463                     x = 0;
    464                     y = 0;
    465                     if (tex * 2 + 1 < texture.length) {
    466                         x = texture[tex*2];
    467                         y = texture[tex*2+1];
    468                     }
    469                     textureArray.push(x);
    470                     textureArray.push(y);
    471 
    472                     // do the normals
    473                     x = 0;
    474                     y = 0;
    475                     z = 1;
    476                     if (nor * 3 + 2 < normal.length) {
    477                         x = normal[nor*3];
    478                         y = normal[nor*3+1];
    479                         z = normal[nor*3+2];
    480                     }
    481                     normalArray.push(x);
    482                     normalArray.push(y);
    483                     normalArray.push(z);
    484 
    485                     facemap[array[i]] = index++;
    486                 }
    487 
    488                 indexArray.push(facemap[array[i]]);
    489                 currentGroup[1]++;
    490             }
    491         }
    492     }
    493 
    494     // set the VBOs
    495     obj.normalObject = obj.ctx.createBuffer();
    496     obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.normalObject);
    497     obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(normalArray), obj.ctx.STATIC_DRAW);
    498 
    499     obj.texCoordObject = obj.ctx.createBuffer();
    500     obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.texCoordObject);
    501     obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(textureArray), obj.ctx.STATIC_DRAW);
    502 
    503     obj.vertexObject = obj.ctx.createBuffer();
    504     obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.vertexObject);
    505     obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(vertexArray), obj.ctx.STATIC_DRAW);
    506 
    507     obj.numIndices = indexArray.length;
    508     obj.indexObject = obj.ctx.createBuffer();
    509     obj.ctx.bindBuffer(obj.ctx.ELEMENT_ARRAY_BUFFER, obj.indexObject);
    510     obj.ctx.bufferData(obj.ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexArray), obj.ctx.STREAM_DRAW);
    511 
    512     obj.groups = groups;
    513 
    514     obj.loaded = true;
    515 }
    516 
    517 // Array of images curently loading
    518 var g_loadingImages = [];
    519 
    520 // Clears all the images currently loading.
    521 // This is used to handle context lost events.
    522 function clearLoadingImages() {
    523     for (var ii = 0; ii < g_loadingImages.length; ++ii) {
    524         g_loadingImages[ii].onload = undefined;
    525     }
    526     g_loadingImages = [];
    527 }
    528 
    529 //
    530 // loadImageTexture
    531 //
    532 // Load the image at the passed url, place it in a new WebGLTexture object and return the WebGLTexture.
    533 //
    534 function loadImageTexture(ctx, url)
    535 {
    536     var texture = ctx.createTexture();
    537     ctx.bindTexture(ctx.TEXTURE_2D, texture);
    538     ctx.texImage2D(ctx.TEXTURE_2D, 0, ctx.RGBA, 1, 1, 0, ctx.RGBA, ctx.UNSIGNED_BYTE, null);
    539     var image = new Image();
    540     g_loadingImages.push(image);
    541     image.onload = function() { doLoadImageTexture(ctx, image, texture) }
    542     image.src = url;
    543     return texture;
    544 }
    545 
    546 function doLoadImageTexture(ctx, image, texture)
    547 {
    548     g_loadingImages.splice(g_loadingImages.indexOf(image), 1);
    549     ctx.bindTexture(ctx.TEXTURE_2D, texture);
    550     ctx.texImage2D(
    551         ctx.TEXTURE_2D, 0, ctx.RGBA, ctx.RGBA, ctx.UNSIGNED_BYTE, image);
    552     ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.LINEAR);
    553     ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.LINEAR);
    554     ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE);
    555     ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE);
    556     //ctx.generateMipmap(ctx.TEXTURE_2D)
    557     ctx.bindTexture(ctx.TEXTURE_2D, null);
    558 }
    559 
    560 //
    561 // Framerate object
    562 //
    563 // This object keeps track of framerate and displays it as the innerHTML text of the
    564 // HTML element with the passed id. Once created you call snapshot at the end
    565 // of every rendering cycle. Every 500ms the framerate is updated in the HTML element.
    566 //
    567 Framerate = function(id)
    568 {
    569     this.numFramerates = 10;
    570     this.framerateUpdateInterval = 500;
    571     this.id = id;
    572 
    573     this.renderTime = -1;
    574     this.framerates = [ ];
    575     self = this;
    576     var fr = function() { self.updateFramerate() }
    577     setInterval(fr, this.framerateUpdateInterval);
    578 }
    579 
    580 Framerate.prototype.updateFramerate = function()
    581 {
    582     var tot = 0;
    583     for (var i = 0; i < this.framerates.length; ++i)
    584         tot += this.framerates[i];
    585 
    586     var framerate = tot / this.framerates.length;
    587     framerate = Math.round(framerate);
    588     document.getElementById(this.id).innerHTML = "Framerate:"+framerate+"fps";
    589 }
    590 
    591 Framerate.prototype.snapshot = function()
    592 {
    593     if (this.renderTime < 0)
    594         this.renderTime = new Date().getTime();
    595     else {
    596         var newTime = new Date().getTime();
    597         var t = newTime - this.renderTime;
    598         if (t == 0)
    599             return;
    600         var framerate = 1000/t;
    601         this.framerates.push(framerate);
    602         while (this.framerates.length > this.numFramerates)
    603             this.framerates.shift();
    604         this.renderTime = newTime;
    605     }
    606 }
    607