scripts/utils3d.js
author Eugen Sawin <sawine@me73.com>
Thu, 21 Apr 2011 03:12:51 +0200
changeset 19 15d61d76daa1
parent 1 92e65293655d
permissions -rwxr-xr-x
Vector based camera. Weird yaw.
     1 //
     2 // initWebGL
     3 //
     4 // Initialize the Canvas element with the passed name as a WebGL object and return the
     5 // WebGLRenderingContext. 
     6 //
     7 // Load shaders with the passed names and create a program with them. Return this program 
     8 // in the 'program' property of the returned context.
     9 //
    10 // For each string in the passed attribs array, bind an attrib with that name at that index.
    11 // Once the attribs are bound, link the program and then use it.
    12 //
    13 // Set the clear color to the passed array (4 values) and set the clear depth to the passed value.
    14 // Enable depth testing and blending with a blend func of (SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
    15 //
    16 function initWebGL(canvasName, vshader, fshader, attribs, clearColor, clearDepth)
    17 {
    18     var canvas = document.getElementById(canvasName);
    19     var gl = canvas.getContext("webkit-3d");
    20 
    21     // create our shaders
    22     var vertexShader = loadShader(gl, vshader);
    23     var fragmentShader = loadShader(gl, fshader);
    24 
    25     if (!vertexShader || !fragmentShader)
    26         return null;
    27 
    28     // Create the program object
    29     gl.program = gl.createProgram();
    30 
    31     if (!gl.program)
    32         return null;
    33 
    34     // Attach our two shaders to the program
    35     gl.attachShader (gl.program, vertexShader);
    36     gl.attachShader (gl.program, fragmentShader);
    37 
    38     // Bind attributes
    39     for (var i in attribs)
    40         gl.bindAttribLocation (gl.program, i, attribs[i]);
    41 
    42     // Link the program
    43     gl.linkProgram(gl.program);
    44 
    45     // Check the link status
    46     var linked = gl.getProgrami(gl.program, gl.LINK_STATUS);
    47     if (!linked) {
    48         // something went wrong with the link
    49         var error = gl.getProgramInfoLog (gl.program);
    50         console.log("Error in program linking:"+error);
    51 
    52         gl.deleteProgram(gl.program);
    53         gl.deleteProgram(fragmentShader);
    54         gl.deleteProgram(vertexShader);
    55 
    56         return null;
    57     }
    58 
    59     gl.useProgram(gl.program);
    60 
    61     gl.clearColor (clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
    62     gl.clearDepth (clearDepth);
    63 
    64     gl.enable(gl.DEPTH_TEST);
    65     gl.enable(gl.BLEND);
    66     gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    67 
    68     return gl;
    69 }
    70 
    71 //
    72 // loadShader
    73 //
    74 // 'shaderId' is the id of a <script> element containing the shader source string.
    75 // Load this shader and return the WebGLShader object corresponding to it.
    76 //
    77 function loadShader(ctx, shaderId)
    78 {
    79     var shaderScript = document.getElementById(shaderId);
    80     if (!shaderScript) {
    81         console.log("*** Error: shader script '"+shaderId+"' not found");
    82         return null;
    83     }
    84         
    85     if (shaderScript.type == "x-shader/x-vertex")
    86         var shaderType = ctx.VERTEX_SHADER;
    87     else if (shaderScript.type == "x-shader/x-fragment")
    88         var shaderType = ctx.FRAGMENT_SHADER;
    89     else {
    90         console.log("*** Error: shader script '"+shaderId+"' of undefined type '"+shaderScript.type+"'");       
    91         return null;
    92     }
    93 
    94     // Create the shader object
    95     var shader = ctx.createShader(shaderType);
    96     if (shader == null) {
    97         console.log("*** Error: unable to create shader '"+shaderId+"'");       
    98         return null;
    99     }
   100 
   101     // Load the shader source
   102     ctx.shaderSource(shader, shaderScript.text);
   103 
   104     // Compile the shader
   105     ctx.compileShader(shader);
   106 
   107     // Check the compile status
   108     var compiled = ctx.getShaderi(shader, ctx.COMPILE_STATUS);
   109     if (!compiled) {
   110         // Something went wrong during compilation; get the error
   111         var error = ctx.getShaderInfoLog(shader);
   112         console.log("*** Error compiling shader '"+shaderId+"':"+error);
   113         ctx.deleteShader(shader);
   114         return null;
   115     }
   116 
   117     return shader;
   118 }
   119 
   120 // 
   121 // makeBox
   122 //
   123 // Create a box with vertices, normals and texCoords. Create VBOs for each as well as the index array.
   124 // Return an object with the following properties:
   125 //
   126 //  normalObject        WebGLBuffer object for normals
   127 //  texCoordObject      WebGLBuffer object for texCoords
   128 //  vertexObject        WebGLBuffer object for vertices
   129 //  indexObject         WebGLBuffer object for indices
   130 //  numIndices          The number of indices in the indexObject
   131 // 
   132 function makeBox(ctx)
   133 {
   134     // box
   135     //    v6----- v5
   136     //   /|      /|
   137     //  v1------v0|
   138     //  | |     | |
   139     //  | |v7---|-|v4
   140     //  |/      |/
   141     //  v2------v3
   142     //
   143     // vertex coords array
   144     var vertices = new WebGLFloatArray(
   145         [  1, 1, 1,  -1, 1, 1,  -1,-1, 1,   1,-1, 1,    // v0-v1-v2-v3 front
   146            1, 1, 1,   1,-1, 1,   1,-1,-1,   1, 1,-1,    // v0-v3-v4-v5 right
   147            1, 1, 1,   1, 1,-1,  -1, 1,-1,  -1, 1, 1,    // v0-v5-v6-v1 top
   148           -1, 1, 1,  -1, 1,-1,  -1,-1,-1,  -1,-1, 1,    // v1-v6-v7-v2 left
   149           -1,-1,-1,   1,-1,-1,   1,-1, 1,  -1,-1, 1,    // v7-v4-v3-v2 bottom
   150            1,-1,-1,  -1,-1,-1,  -1, 1,-1,   1, 1,-1 ]   // v4-v7-v6-v5 back
   151     );
   152 
   153     // normal array
   154     var normals = new WebGLFloatArray(
   155         [  0, 0, 1,   0, 0, 1,   0, 0, 1,   0, 0, 1,     // v0-v1-v2-v3 front
   156            1, 0, 0,   1, 0, 0,   1, 0, 0,   1, 0, 0,     // v0-v3-v4-v5 right
   157            0, 1, 0,   0, 1, 0,   0, 1, 0,   0, 1, 0,     // v0-v5-v6-v1 top
   158           -1, 0, 0,  -1, 0, 0,  -1, 0, 0,  -1, 0, 0,     // v1-v6-v7-v2 left
   159            0,-1, 0,   0,-1, 0,   0,-1, 0,   0,-1, 0,     // v7-v4-v3-v2 bottom
   160            0, 0,-1,   0, 0,-1,   0, 0,-1,   0, 0,-1 ]    // v4-v7-v6-v5 back
   161        );
   162 
   163 
   164     // texCoord array
   165     var texCoords = new WebGLFloatArray(
   166         [  1, 1,   0, 1,   0, 0,   1, 0,    // v0-v1-v2-v3 front
   167            0, 1,   0, 0,   1, 0,   1, 1,    // v0-v3-v4-v5 right
   168            1, 0,   1, 1,   0, 1,   0, 0,    // v0-v5-v6-v1 top
   169            1, 1,   0, 1,   0, 0,   1, 0,    // v1-v6-v7-v2 left
   170            0, 0,   1, 0,   1, 1,   0, 1,    // v7-v4-v3-v2 bottom
   171            0, 0,   1, 0,   1, 1,   0, 1 ]   // v4-v7-v6-v5 back
   172        );
   173 
   174     // index array
   175     var indices = new WebGLUnsignedByteArray(
   176         [  0, 1, 2,   0, 2, 3,    // front
   177            4, 5, 6,   4, 6, 7,    // right
   178            8, 9,10,   8,10,11,    // top
   179           12,13,14,  12,14,15,    // left
   180           16,17,18,  16,18,19,    // bottom
   181           20,21,22,  20,22,23 ]   // back
   182       );
   183 
   184     var retval = { };
   185     
   186     retval.normalObject = ctx.createBuffer();
   187     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
   188     ctx.bufferData(ctx.ARRAY_BUFFER, normals, ctx.STATIC_DRAW);
   189     
   190     retval.texCoordObject = ctx.createBuffer();
   191     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject);
   192     ctx.bufferData(ctx.ARRAY_BUFFER, texCoords, ctx.STATIC_DRAW);
   193 
   194     retval.vertexObject = ctx.createBuffer();
   195     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
   196     ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW);
   197     
   198     ctx.bindBuffer(ctx.ARRAY_BUFFER, 0);
   199 
   200     retval.indexObject = ctx.createBuffer();
   201     ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject);
   202     ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, indices, ctx.STATIC_DRAW);
   203     ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, 0);
   204     
   205     retval.numIndices = indices.length;
   206 
   207     return retval;
   208 }
   209 
   210 // 
   211 // makeSphere
   212 //
   213 // Create a sphere with the passed number of latitude and longitude bands and the passed radius. 
   214 // Sphere has vertices, normals and texCoords. Create VBOs for each as well as the index array.
   215 // Return an object with the following properties:
   216 //
   217 //  normalObject        WebGLBuffer object for normals
   218 //  texCoordObject      WebGLBuffer object for texCoords
   219 //  vertexObject        WebGLBuffer object for vertices
   220 //  indexObject         WebGLBuffer object for indices
   221 //  numIndices          The number of indices in the indexObject
   222 // 
   223 function makeSphere(ctx, radius, lats, longs)
   224 {
   225     var geometryData = [ ];
   226     var normalData = [ ];
   227     var texCoordData = [ ];
   228     var indexData = [ ];
   229     
   230     for (var latNumber = 0; latNumber <= lats; ++latNumber) {
   231         for (var longNumber = 0; longNumber <= longs; ++longNumber) {
   232             var theta = latNumber * Math.PI / lats;
   233             var phi = longNumber * 2 * Math.PI / longs;
   234             var sinTheta = Math.sin(theta);
   235             var sinPhi = Math.sin(phi);
   236             var cosTheta = Math.cos(theta);
   237             var cosPhi = Math.cos(phi);
   238             
   239             var x = cosPhi * sinTheta;
   240             var y = cosTheta;
   241             var z = sinPhi * sinTheta;
   242             var u = 1-(longNumber/longs);
   243             var v = latNumber/lats;
   244             
   245             normalData.push(x);
   246             normalData.push(y);
   247             normalData.push(z);
   248             texCoordData.push(u);
   249             texCoordData.push(v);
   250             geometryData.push(radius * x);
   251             geometryData.push(radius * y);
   252             geometryData.push(radius * z);
   253         }
   254     }
   255     
   256     longs += 1;
   257     for (var latNumber = 0; latNumber < lats; ++latNumber) {
   258         for (var longNumber = 0; longNumber < longs; ++longNumber) {
   259             var first = (latNumber * longs) + (longNumber % longs);
   260             var second = first + longs;
   261             indexData.push(first);
   262             indexData.push(second);
   263             indexData.push(first+1);
   264 
   265             indexData.push(second);
   266             indexData.push(second+1);
   267             indexData.push(first+1);
   268         }
   269     }
   270     
   271     var retval = { };
   272     
   273     retval.normalObject = ctx.createBuffer();
   274     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
   275     ctx.bufferData(ctx.ARRAY_BUFFER, new WebGLFloatArray(normalData), ctx.STATIC_DRAW);
   276 
   277     retval.texCoordObject = ctx.createBuffer();
   278     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject);
   279     ctx.bufferData(ctx.ARRAY_BUFFER, new WebGLFloatArray(texCoordData), ctx.STATIC_DRAW);
   280 
   281     retval.vertexObject = ctx.createBuffer();
   282     ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
   283     ctx.bufferData(ctx.ARRAY_BUFFER, new WebGLFloatArray(geometryData), ctx.STATIC_DRAW);
   284     
   285     retval.numIndices = indexData.length;
   286     retval.indexObject = ctx.createBuffer();
   287     ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject);
   288     ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(indexData), ctx.STREAM_DRAW);
   289     
   290     return retval;
   291 }
   292 
   293 //
   294 // loadObj
   295 //
   296 // Load a .obj file from the passed URL. Return an object with a 'loaded' property set to false.
   297 // When the object load is complete, the 'loaded' property becomes true and the following 
   298 // properties are set:
   299 //
   300 //  normalObject        WebGLBuffer object for normals
   301 //  texCoordObject      WebGLBuffer object for texCoords
   302 //  vertexObject        WebGLBuffer object for vertices
   303 //  indexObject         WebGLBuffer object for indices
   304 //  numIndices          The number of indices in the indexObject
   305 //  
   306 function loadObj(ctx, url)
   307 {
   308     var obj = { loaded : false };
   309     obj.ctx = ctx;
   310     var req = new XMLHttpRequest();
   311     req.obj = obj;
   312     req.onreadystatechange = function () { processLoadObj(req) };
   313     req.open("GET", url, true);
   314     req.send(null);
   315     return obj;
   316 }
   317 
   318 function processLoadObj(req) 
   319 {
   320     console.log("req="+req)
   321     // only if req shows "complete"
   322     if (req.readyState == 4) {
   323         doLoadObj(req.obj, req.responseText);
   324     }
   325 }
   326 
   327 function doLoadObj(obj, text)
   328 {
   329     vertexArray = [ ];
   330     normalArray = [ ];
   331     textureArray = [ ];
   332     indexArray = [ ];
   333     
   334     var vertex = [ ];
   335     var normal = [ ];
   336     var texture = [ ];
   337     var facemap = { };
   338     var index = 0;
   339         
   340     var lines = text.split("\n");
   341     for (var lineIndex in lines) {
   342         var line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, "");
   343         
   344         // ignore comments
   345         if (line[0] == "#")
   346             continue;
   347             
   348         var array = line.split(" ");
   349         if (array[0] == "v") {
   350             // vertex
   351             vertex.push(parseFloat(array[1]));
   352             vertex.push(parseFloat(array[2]));
   353             vertex.push(parseFloat(array[3]));
   354         }
   355         else if (array[0] == "vt") {
   356             // normal
   357             texture.push(parseFloat(array[1]));
   358             texture.push(parseFloat(array[2]));
   359         }
   360         else if (array[0] == "vn") {
   361             // normal
   362             normal.push(parseFloat(array[1]));
   363             normal.push(parseFloat(array[2]));
   364             normal.push(parseFloat(array[3]));
   365         }
   366         else if (array[0] == "f") {
   367             // face
   368             if (array.length != 4) {
   369                 console.log("*** Error: face '"+line+"' not handled");
   370                 continue;
   371             }
   372             
   373             for (var i = 1; i < 4; ++i) {
   374                 if (!(array[i] in facemap)) {
   375                     // add a new entry to the map and arrays
   376                     var f = array[i].split("/");
   377                     var vtx, nor, tex;
   378                     
   379                     if (f.length == 1) {
   380                         vtx = parseInt(f[0]) - 1;
   381                         nor = vtx;
   382                         tex = vtx;
   383                     }
   384                     else if (f.length = 3) {
   385                         vtx = parseInt(f[0]) - 1;
   386                         tex = parseInt(f[1]) - 1;
   387                         nor = parseInt(f[2]) - 1;
   388                     }
   389                     else {
   390                         console.log("*** Error: did not understand face '"+array[i]+"'");
   391                         return null;
   392                     }
   393                     
   394                     // do the vertices
   395                     var x = 0;
   396                     var y = 0;
   397                     var z = 0;
   398                     if (vtx * 3 + 2 < vertex.length) {
   399                         x = vertex[vtx*3];
   400                         y = vertex[vtx*3+1];
   401                         z = vertex[vtx*3+2];
   402                     }
   403                     vertexArray.push(x);
   404                     vertexArray.push(y);
   405                     vertexArray.push(z);
   406                     
   407                     // do the textures
   408                     x = 0;
   409                     y = 0;
   410                     if (tex * 2 + 1 < texture.length) {
   411                         x = texture[tex*2];
   412                         y = texture[tex*2+1];
   413                     }
   414                     textureArray.push(x);
   415                     textureArray.push(y);
   416                     
   417                     // do the normals
   418                     x = 0;
   419                     y = 0;
   420                     z = 1;
   421                     if (nor * 3 + 2 < normal.length) {
   422                         x = normal[nor*3];
   423                         y = normal[nor*3+1];
   424                         z = normal[nor*3+2];
   425                     }
   426                     normalArray.push(x);
   427                     normalArray.push(y);
   428                     normalArray.push(z);
   429                     
   430                     facemap[array[i]] = index++;
   431                 }
   432                 
   433                 indexArray.push(facemap[array[i]]);
   434             }
   435         }
   436     }
   437 
   438     // set the VBOs
   439     obj.normalObject = obj.ctx.createBuffer();
   440     obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.normalObject);
   441     obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new WebGLFloatArray(normalArray), obj.ctx.STATIC_DRAW);
   442 
   443     obj.texCoordObject = obj.ctx.createBuffer();
   444     obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.texCoordObject);
   445     obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new WebGLFloatArray(textureArray), obj.ctx.STATIC_DRAW);
   446 
   447     obj.vertexObject = obj.ctx.createBuffer();
   448     obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.vertexObject);
   449     obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new WebGLFloatArray(vertexArray), obj.ctx.STATIC_DRAW);
   450     
   451     obj.numIndices = indexArray.length;
   452     obj.indexObject = obj.ctx.createBuffer();
   453     obj.ctx.bindBuffer(obj.ctx.ELEMENT_ARRAY_BUFFER, obj.indexObject);
   454     obj.ctx.bufferData(obj.ctx.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(indexArray), obj.ctx.STREAM_DRAW);
   455     
   456     obj.loaded = true;
   457 }
   458 
   459 //
   460 // loadImageTexture
   461 //
   462 // Load the image at the passed url, place it in a new WebGLTexture object and return the WebGLTexture.
   463 //
   464 function loadImageTexture(ctx, url)
   465 {
   466     var texture = ctx.createTexture();
   467     texture.image = new Image();
   468     texture.image.onload = function() { doLoadImageTexture(ctx, texture.image, texture) }
   469     texture.image.src = url;
   470     return texture;
   471 }
   472 
   473 function doLoadImageTexture(ctx, image, texture)
   474 {
   475     ctx.enable(ctx.TEXTURE_2D);
   476     ctx.bindTexture(ctx.TEXTURE_2D, texture);
   477     ctx.texImage2D(ctx.TEXTURE_2D, 0, image);
   478     ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.LINEAR);
   479     ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.LINEAR_MIPMAP_LINEAR);
   480     ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE);
   481     ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE);
   482     ctx.generateMipmap(ctx.TEXTURE_2D)
   483     ctx.bindTexture(ctx.TEXTURE_2D, 0);
   484 }
   485 
   486 //
   487 // Framerate object
   488 //
   489 // This object keeps track of framerate and displays it as the innerHTML text of the 
   490 // HTML element with the passed id. Once created you call snapshot at the end
   491 // of every rendering cycle. Every 500ms the framerate is updated in the HTML element.
   492 //
   493 Framerate = function(id)
   494 {
   495     this.numFramerates = 10;
   496     this.framerateUpdateInterval = 500;
   497     this.id = id;
   498 
   499     this.renderTime = -1;
   500     this.framerates = [ ];
   501     self = this;
   502     var fr = function() { self.updateFramerate() }
   503     setInterval(fr, this.framerateUpdateInterval);
   504 }
   505 
   506 Framerate.prototype.updateFramerate = function()
   507 {
   508     var tot = 0;
   509     for (var i = 0; i < this.framerates.length; ++i)
   510         tot += this.framerates[i];
   511         
   512     var framerate = tot / this.framerates.length;
   513     framerate = Math.round(framerate);
   514     document.getElementById(this.id).innerHTML = "Framerate:"+framerate+"fps";
   515 }
   516 
   517 Framerate.prototype.snapshot = function()
   518 {
   519     if (this.renderTime < 0)
   520         this.renderTime = new Date().getTime();
   521     else {
   522         var newTime = new Date().getTime();
   523         var t = newTime - this.renderTime;
   524         var framerate = 1000/t;
   525         this.framerates.push(framerate);
   526         while (this.framerates.length > this.numFramerates)
   527             this.framerates.shift();
   528         this.renderTime = newTime;
   529     }
   530 }