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. 31function initWebGL(canvasName, vshader, fshader, attribs, clearColor, clearDepth) 32{ 33 var canvas = document.getElementById(canvasName); 34 return gl = WebGLUtils.setupWebGL(canvas); 35} 36 37function 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// 56function 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// 108function 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// 159function 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// 250function 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 320var g_loadingObjects = []; 321 322// Clears all the Objects currently loading. 323// This is used to handle context lost events. 324function 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// 344function 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 357function 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 367function 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 518var g_loadingImages = []; 519 520// Clears all the images currently loading. 521// This is used to handle context lost events. 522function 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// 534function 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 546function 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// 567Framerate = 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 580Framerate.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 591Framerate.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