Refact.
4 // Initialize the Canvas element with the passed name as a WebGL object and return the
5 // WebGLRenderingContext.
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.
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.
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)
16 function initWebGL(canvasName, vshader, fshader, attribs, clearColor, clearDepth)
18 var canvas = document.getElementById(canvasName);
19 var gl = canvas.getContext("webkit-3d");
22 var vertexShader = loadShader(gl, vshader);
23 var fragmentShader = loadShader(gl, fshader);
25 if (!vertexShader || !fragmentShader)
28 // Create the program object
29 gl.program = gl.createProgram();
34 // Attach our two shaders to the program
35 gl.attachShader (gl.program, vertexShader);
36 gl.attachShader (gl.program, fragmentShader);
39 for (var i in attribs)
40 gl.bindAttribLocation (gl.program, i, attribs[i]);
43 gl.linkProgram(gl.program);
45 // Check the link status
46 var linked = gl.getProgrami(gl.program, gl.LINK_STATUS);
48 // something went wrong with the link
49 var error = gl.getProgramInfoLog (gl.program);
50 console.log("Error in program linking:"+error);
52 gl.deleteProgram(gl.program);
53 gl.deleteProgram(fragmentShader);
54 gl.deleteProgram(vertexShader);
59 gl.useProgram(gl.program);
61 gl.clearColor (clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
62 gl.clearDepth (clearDepth);
64 gl.enable(gl.DEPTH_TEST);
66 gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
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.
77 function loadShader(ctx, shaderId)
79 var shaderScript = document.getElementById(shaderId);
81 console.log("*** Error: shader script '"+shaderId+"' not found");
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;
90 console.log("*** Error: shader script '"+shaderId+"' of undefined type '"+shaderScript.type+"'");
94 // Create the shader object
95 var shader = ctx.createShader(shaderType);
97 console.log("*** Error: unable to create shader '"+shaderId+"'");
101 // Load the shader source
102 ctx.shaderSource(shader, shaderScript.text);
104 // Compile the shader
105 ctx.compileShader(shader);
107 // Check the compile status
108 var compiled = ctx.getShaderi(shader, ctx.COMPILE_STATUS);
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);
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:
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
132 function makeBox(ctx)
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
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
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
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
186 retval.normalObject = ctx.createBuffer();
187 ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
188 ctx.bufferData(ctx.ARRAY_BUFFER, normals, ctx.STATIC_DRAW);
190 retval.texCoordObject = ctx.createBuffer();
191 ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject);
192 ctx.bufferData(ctx.ARRAY_BUFFER, texCoords, ctx.STATIC_DRAW);
194 retval.vertexObject = ctx.createBuffer();
195 ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
196 ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW);
198 ctx.bindBuffer(ctx.ARRAY_BUFFER, 0);
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);
205 retval.numIndices = indices.length;
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:
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
223 function makeSphere(ctx, radius, lats, longs)
225 var geometryData = [ ];
226 var normalData = [ ];
227 var texCoordData = [ ];
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);
239 var x = cosPhi * sinTheta;
241 var z = sinPhi * sinTheta;
242 var u = 1-(longNumber/longs);
243 var v = latNumber/lats;
248 texCoordData.push(u);
249 texCoordData.push(v);
250 geometryData.push(radius * x);
251 geometryData.push(radius * y);
252 geometryData.push(radius * z);
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);
265 indexData.push(second);
266 indexData.push(second+1);
267 indexData.push(first+1);
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);
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);
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);
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);
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:
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
306 function loadObj(ctx, url)
308 var obj = { loaded : false };
310 var req = new XMLHttpRequest();
312 req.onreadystatechange = function () { processLoadObj(req) };
313 req.open("GET", url, true);
318 function processLoadObj(req)
320 console.log("req="+req)
321 // only if req shows "complete"
322 if (req.readyState == 4) {
323 doLoadObj(req.obj, req.responseText);
327 function doLoadObj(obj, text)
340 var lines = text.split("\n");
341 for (var lineIndex in lines) {
342 var line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, "");
348 var array = line.split(" ");
349 if (array[0] == "v") {
351 vertex.push(parseFloat(array[1]));
352 vertex.push(parseFloat(array[2]));
353 vertex.push(parseFloat(array[3]));
355 else if (array[0] == "vt") {
357 texture.push(parseFloat(array[1]));
358 texture.push(parseFloat(array[2]));
360 else if (array[0] == "vn") {
362 normal.push(parseFloat(array[1]));
363 normal.push(parseFloat(array[2]));
364 normal.push(parseFloat(array[3]));
366 else if (array[0] == "f") {
368 if (array.length != 4) {
369 console.log("*** Error: face '"+line+"' not handled");
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("/");
380 vtx = parseInt(f[0]) - 1;
384 else if (f.length = 3) {
385 vtx = parseInt(f[0]) - 1;
386 tex = parseInt(f[1]) - 1;
387 nor = parseInt(f[2]) - 1;
390 console.log("*** Error: did not understand face '"+array[i]+"'");
398 if (vtx * 3 + 2 < vertex.length) {
410 if (tex * 2 + 1 < texture.length) {
412 y = texture[tex*2+1];
414 textureArray.push(x);
415 textureArray.push(y);
421 if (nor * 3 + 2 < normal.length) {
430 facemap[array[i]] = index++;
433 indexArray.push(facemap[array[i]]);
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);
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);
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);
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);
462 // Load the image at the passed url, place it in a new WebGLTexture object and return the WebGLTexture.
464 function loadImageTexture(ctx, url)
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;
473 function doLoadImageTexture(ctx, image, texture)
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);
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.
493 Framerate = function(id)
495 this.numFramerates = 10;
496 this.framerateUpdateInterval = 500;
499 this.renderTime = -1;
500 this.framerates = [ ];
502 var fr = function() { self.updateFramerate() }
503 setInterval(fr, this.framerateUpdateInterval);
506 Framerate.prototype.updateFramerate = function()
509 for (var i = 0; i < this.framerates.length; ++i)
510 tot += this.framerates[i];
512 var framerate = tot / this.framerates.length;
513 framerate = Math.round(framerate);
514 document.getElementById(this.id).innerHTML = "Framerate:"+framerate+"fps";
517 Framerate.prototype.snapshot = function()
519 if (this.renderTime < 0)
520 this.renderTime = new Date().getTime();
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;