|
1 /* |
|
2 Copyright (C) 2011 Apple Computer, 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 COMPUTER, 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 COMPUTER, 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 function webglTestLog(msg) { |
|
27 if (window.console && window.console.log) { |
|
28 window.console.log(msg); |
|
29 } |
|
30 if (document.getElementById("console")) { |
|
31 var log = document.getElementById("console"); |
|
32 log.innerHTML += msg + "<br>"; |
|
33 } |
|
34 } |
|
35 |
|
36 // |
|
37 // create3DContext |
|
38 // |
|
39 // Returns the WebGLRenderingContext for any known implementation. |
|
40 // |
|
41 function create3DContext(canvas, attributes) |
|
42 { |
|
43 if (!canvas) |
|
44 canvas = document.createElement("canvas"); |
|
45 var names = ["webgl", "experimental-webgl"]; |
|
46 var context = null; |
|
47 for (var i = 0; i < names.length; ++i) { |
|
48 try { |
|
49 context = canvas.getContext(names[i], attributes); |
|
50 } catch (e) { |
|
51 } |
|
52 if (context) { |
|
53 break; |
|
54 } |
|
55 } |
|
56 if (!context) { |
|
57 throw "Unable to fetch WebGL rendering context for Canvas"; |
|
58 } |
|
59 return context; |
|
60 } |
|
61 |
|
62 function createGLErrorWrapper(context, fname) { |
|
63 return function() { |
|
64 var rv = context[fname].apply(context, arguments); |
|
65 var err = context.getError(); |
|
66 if (err != 0) |
|
67 throw "GL error " + err + " in " + fname; |
|
68 return rv; |
|
69 }; |
|
70 } |
|
71 |
|
72 function create3DContextWithWrapperThatThrowsOnGLError(canvas, attributes) { |
|
73 var context = create3DContext(canvas, attributes); |
|
74 // Thanks to Ilmari Heikkinen for the idea on how to implement this so elegantly. |
|
75 var wrap = {}; |
|
76 for (var i in context) { |
|
77 try { |
|
78 if (typeof context[i] == 'function') { |
|
79 wrap[i] = createGLErrorWrapper(context, i); |
|
80 } else { |
|
81 wrap[i] = context[i]; |
|
82 } |
|
83 } catch (e) { |
|
84 webglTestLog("createContextWrapperThatThrowsOnGLError: Error accessing " + i); |
|
85 } |
|
86 } |
|
87 wrap.getError = function() { |
|
88 return context.getError(); |
|
89 }; |
|
90 return wrap; |
|
91 } |
|
92 |
|
93 function getGLErrorAsString(ctx, err) { |
|
94 if (err === ctx.NO_ERROR) { |
|
95 return "NO_ERROR"; |
|
96 } |
|
97 for (var name in ctx) { |
|
98 if (ctx[name] === err) { |
|
99 return name; |
|
100 } |
|
101 } |
|
102 return "0x" + err.toString(16); |
|
103 } |
|
104 |
|
105 // Pass undefined for glError to test that it at least throws some error |
|
106 function shouldGenerateGLError(ctx, glErrors, evalStr) { |
|
107 if (!glErrors.length) { |
|
108 glErrors = [glErrors]; |
|
109 } |
|
110 var exception; |
|
111 try { |
|
112 eval(evalStr); |
|
113 } catch (e) { |
|
114 exception = e; |
|
115 } |
|
116 if (exception) { |
|
117 testFailed(evalStr + " threw exception " + exception); |
|
118 } else { |
|
119 var err = ctx.getError(); |
|
120 if (glErrors.indexOf(err) < 0) { |
|
121 var errStrs = []; |
|
122 for (var ii = 0; ii < glErrors.length; ++ii) { |
|
123 errStrs.push(getGLErrorAsString(ctx, glErrors[ii])); |
|
124 } |
|
125 testFailed(evalStr + " expected: " + errStrs.join(" or ") + ". Was " + getGLErrorAsString(ctx, err) + "."); |
|
126 } else { |
|
127 testPassed(evalStr + " generated expected GL error: " + getGLErrorAsString(ctx, err) + "."); |
|
128 } |
|
129 } |
|
130 } |
|
131 |
|
132 /** |
|
133 * Tests that the first error GL returns is the specified error. |
|
134 * @param {!WebGLContext} gl The WebGLContext to use. |
|
135 * @param {number|!Array.<number>} glError The expected gl |
|
136 * error. Multiple errors can be passed in using an |
|
137 * array. |
|
138 * @param {string} opt_msg Optional additional message. |
|
139 */ |
|
140 function glErrorShouldBe(gl, glErrors, opt_msg) { |
|
141 if (!glErrors.length) { |
|
142 glErrors = [glErrors]; |
|
143 } |
|
144 opt_msg = opt_msg || ""; |
|
145 var err = gl.getError(); |
|
146 var ndx = glErrors.indexOf(err); |
|
147 if (ndx < 0) { |
|
148 if (glErrors.length == 1) { |
|
149 testFailed("getError expected: " + getGLErrorAsString(gl, glErrors[0]) + |
|
150 ". Was " + getGLErrorAsString(gl, err) + " : " + opt_msg); |
|
151 } else { |
|
152 var errs = []; |
|
153 for (var ii = 0; ii < glErrors.length; ++ii) { |
|
154 errs.push(getGLErrorAsString(gl, glErrors[ii])); |
|
155 } |
|
156 testFailed("getError expected one of: [" + errs.join(", ") + |
|
157 "]. Was " + getGLErrorAsString(gl, err) + " : " + opt_msg); |
|
158 } |
|
159 } else { |
|
160 testPassed("getError was expected value: " + |
|
161 getGLErrorAsString(gl, err) + " : " + opt_msg); |
|
162 } |
|
163 }; |
|
164 |
|
165 // |
|
166 // createProgram |
|
167 // |
|
168 // Create and return a program object, attaching each of the given shaders. |
|
169 // |
|
170 // If attribs are given, bind an attrib with that name at that index. |
|
171 // |
|
172 function createProgram(gl, vshaders, fshaders, attribs) |
|
173 { |
|
174 if (typeof(vshaders) == "string") |
|
175 vshaders = [vshaders]; |
|
176 if (typeof(fshaders) == "string") |
|
177 fshaders = [fshaders]; |
|
178 |
|
179 var shaders = []; |
|
180 var i; |
|
181 |
|
182 for (i = 0; i < vshaders.length; ++i) { |
|
183 var shader = loadShader(gl, vshaders[i], gl.VERTEX_SHADER); |
|
184 if (!shader) |
|
185 return null; |
|
186 shaders.push(shader); |
|
187 } |
|
188 |
|
189 for (i = 0; i < fshaders.length; ++i) { |
|
190 var shader = loadShader(gl, fshaders[i], gl.FRAGMENT_SHADER); |
|
191 if (!shader) |
|
192 return null; |
|
193 shaders.push(shader); |
|
194 } |
|
195 |
|
196 var prog = gl.createProgram(); |
|
197 for (i = 0; i < shaders.length; ++i) { |
|
198 gl.attachShader(prog, shaders[i]); |
|
199 } |
|
200 |
|
201 if (attribs) { |
|
202 for (var i = 0; i < attribs.length; ++i) { |
|
203 gl.bindAttribLocation(prog, i, attribs[i]); |
|
204 } |
|
205 } |
|
206 |
|
207 gl.linkProgram(prog); |
|
208 |
|
209 // Check the link status |
|
210 var linked = gl.getProgramParameter(prog, gl.LINK_STATUS); |
|
211 if (!linked) { |
|
212 // something went wrong with the link |
|
213 var error = gl.getProgramInfoLog(prog); |
|
214 webglTestLog("Error in program linking:" + error); |
|
215 |
|
216 gl.deleteProgram(prog); |
|
217 for (i = 0; i < shaders.length; ++i) |
|
218 gl.deleteShader(shaders[i]); |
|
219 return null; |
|
220 } |
|
221 |
|
222 return prog; |
|
223 } |
|
224 |
|
225 // |
|
226 // initWebGL |
|
227 // |
|
228 // Initialize the Canvas element with the passed name as a WebGL object and return the |
|
229 // WebGLRenderingContext. |
|
230 // |
|
231 // Load shaders with the passed names and create a program with them. Return this program |
|
232 // in the 'program' property of the returned context. |
|
233 // |
|
234 // For each string in the passed attribs array, bind an attrib with that name at that index. |
|
235 // Once the attribs are bound, link the program and then use it. |
|
236 // |
|
237 // Set the clear color to the passed array (4 values) and set the clear depth to the passed value. |
|
238 // Enable depth testing and blending with a blend func of (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) |
|
239 // |
|
240 function initWebGL(canvasName, vshader, fshader, attribs, clearColor, clearDepth, contextAttribs) |
|
241 { |
|
242 var canvas = document.getElementById(canvasName); |
|
243 var gl = create3DContext(canvas, contextAttribs); |
|
244 if (!gl) { |
|
245 alert("No WebGL context found"); |
|
246 return null; |
|
247 } |
|
248 |
|
249 // Create the program object |
|
250 gl.program = createProgram(gl, vshader, fshader, attribs); |
|
251 if (!gl.program) |
|
252 return null; |
|
253 |
|
254 gl.useProgram(gl.program); |
|
255 |
|
256 gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); |
|
257 gl.clearDepth(clearDepth); |
|
258 |
|
259 gl.enable(gl.DEPTH_TEST); |
|
260 gl.enable(gl.BLEND); |
|
261 gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); |
|
262 |
|
263 return gl; |
|
264 } |
|
265 |
|
266 // |
|
267 // getShaderSource |
|
268 // |
|
269 // Load the source from the passed shader file. |
|
270 // |
|
271 function getShaderSource(file) |
|
272 { |
|
273 var xhr = new XMLHttpRequest(); |
|
274 xhr.open("GET", file, false); |
|
275 xhr.send(); |
|
276 return xhr.responseText; |
|
277 } |
|
278 |
|
279 |
|
280 // |
|
281 // loadShader |
|
282 // |
|
283 // 'shader' is either the id of a <script> element containing the shader source |
|
284 // string, the shader string itself, or the URL of a file containing the shader |
|
285 // source. Load this shader and return the WebGLShader object corresponding to |
|
286 // it. |
|
287 // |
|
288 function loadShader(ctx, shaderId, shaderType, isFile) |
|
289 { |
|
290 var shaderSource = ""; |
|
291 |
|
292 if (isFile) |
|
293 shaderSource = getShaderSource(shaderId); |
|
294 else { |
|
295 var shaderScript = document.getElementById(shaderId); |
|
296 if (!shaderScript) { |
|
297 shaderSource = shaderId; |
|
298 } else { |
|
299 if (shaderScript.type == "x-shader/x-vertex") { |
|
300 shaderType = ctx.VERTEX_SHADER; |
|
301 } else if (shaderScript.type == "x-shader/x-fragment") { |
|
302 shaderType = ctx.FRAGMENT_SHADER; |
|
303 } else if (shaderType != ctx.VERTEX_SHADER && shaderType != ctx.FRAGMENT_SHADER) { |
|
304 webglTestLog("*** Error: unknown shader type"); |
|
305 return null; |
|
306 } |
|
307 |
|
308 shaderSource = shaderScript.text; |
|
309 } |
|
310 } |
|
311 |
|
312 // Create the shader object |
|
313 var shader = ctx.createShader(shaderType); |
|
314 if (shader == null) { |
|
315 webglTestLog("*** Error: unable to create shader '"+shaderId+"'"); |
|
316 return null; |
|
317 } |
|
318 |
|
319 // Load the shader source |
|
320 ctx.shaderSource(shader, shaderSource); |
|
321 |
|
322 // Compile the shader |
|
323 ctx.compileShader(shader); |
|
324 |
|
325 // Check the compile status |
|
326 var compiled = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS); |
|
327 if (!compiled) { |
|
328 // Something went wrong during compilation; get the error |
|
329 var error = ctx.getShaderInfoLog(shader); |
|
330 webglTestLog("*** Error compiling shader '"+shader+"':"+error); |
|
331 ctx.deleteShader(shader); |
|
332 return null; |
|
333 } |
|
334 |
|
335 return shader; |
|
336 } |
|
337 |
|
338 function loadShaderFromFile(ctx, file, type) |
|
339 { |
|
340 return loadShader(ctx, file, type, true); |
|
341 } |
|
342 |
|
343 function loadShaderFromScript(ctx, script) |
|
344 { |
|
345 return loadShader(ctx, script, 0, false); |
|
346 } |
|
347 |
|
348 function loadStandardProgram(context) { |
|
349 var program = context.createProgram(); |
|
350 context.attachShader(program, loadStandardVertexShader(context)); |
|
351 context.attachShader(program, loadStandardFragmentShader(context)); |
|
352 context.linkProgram(program); |
|
353 return program; |
|
354 } |
|
355 |
|
356 function loadProgram(context, vertexShaderPath, fragmentShaderPath, isFile) { |
|
357 isFile = (isFile === undefined) ? true : isFile; |
|
358 var program = context.createProgram(); |
|
359 context.attachShader(program, loadShader(context, vertexShaderPath, context.VERTEX_SHADER, isFile)); |
|
360 context.attachShader(program, loadShader(context, fragmentShaderPath, context.FRAGMENT_SHADER, isFile)); |
|
361 context.linkProgram(program); |
|
362 return program; |
|
363 } |
|
364 |
|
365 var getBasePathForResources = function() { |
|
366 var expectedBase = "webgl-test.js"; |
|
367 var scripts = document.getElementsByTagName('script'); |
|
368 for (var script, i = 0; script = scripts[i]; i++) { |
|
369 var src = script.src; |
|
370 var l = src.length; |
|
371 if (src.substr(l - expectedBase.length) == expectedBase) { |
|
372 return src.substr(0, l - expectedBase.length); |
|
373 } |
|
374 } |
|
375 throw 'oops'; |
|
376 }; |
|
377 |
|
378 |
|
379 function loadStandardVertexShader(context) { |
|
380 return loadShader( |
|
381 context, |
|
382 getBasePathForResources() + "vertexShader.vert", |
|
383 context.VERTEX_SHADER, |
|
384 true); |
|
385 } |
|
386 |
|
387 function loadStandardFragmentShader(context) { |
|
388 return loadShader( |
|
389 context, |
|
390 getBasePathForResources() + "fragmentShader.frag", |
|
391 context.FRAGMENT_SHADER, |
|
392 true); |
|
393 } |
|
394 |
|
395 // |
|
396 // makeBox |
|
397 // |
|
398 // Create a box with vertices, normals and texCoords. Create VBOs for each as well as the index array. |
|
399 // Return an object with the following properties: |
|
400 // |
|
401 // normalObject WebGLBuffer object for normals |
|
402 // texCoordObject WebGLBuffer object for texCoords |
|
403 // vertexObject WebGLBuffer object for vertices |
|
404 // indexObject WebGLBuffer object for indices |
|
405 // numIndices The number of indices in the indexObject |
|
406 // |
|
407 function makeBox(ctx) |
|
408 { |
|
409 // box |
|
410 // v6----- v5 |
|
411 // /| /| |
|
412 // v1------v0| |
|
413 // | | | | |
|
414 // | |v7---|-|v4 |
|
415 // |/ |/ |
|
416 // v2------v3 |
|
417 // |
|
418 // vertex coords array |
|
419 var vertices = new Float32Array( |
|
420 [ 1, 1, 1, -1, 1, 1, -1,-1, 1, 1,-1, 1, // v0-v1-v2-v3 front |
|
421 1, 1, 1, 1,-1, 1, 1,-1,-1, 1, 1,-1, // v0-v3-v4-v5 right |
|
422 1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1, // v0-v5-v6-v1 top |
|
423 -1, 1, 1, -1, 1,-1, -1,-1,-1, -1,-1, 1, // v1-v6-v7-v2 left |
|
424 -1,-1,-1, 1,-1,-1, 1,-1, 1, -1,-1, 1, // v7-v4-v3-v2 bottom |
|
425 1,-1,-1, -1,-1,-1, -1, 1,-1, 1, 1,-1 ] // v4-v7-v6-v5 back |
|
426 ); |
|
427 |
|
428 // normal array |
|
429 var normals = new Float32Array( |
|
430 [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0-v1-v2-v3 front |
|
431 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right |
|
432 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0-v5-v6-v1 top |
|
433 -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1-v6-v7-v2 left |
|
434 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, // v7-v4-v3-v2 bottom |
|
435 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1 ] // v4-v7-v6-v5 back |
|
436 ); |
|
437 |
|
438 |
|
439 // texCoord array |
|
440 var texCoords = new Float32Array( |
|
441 [ 1, 1, 0, 1, 0, 0, 1, 0, // v0-v1-v2-v3 front |
|
442 0, 1, 0, 0, 1, 0, 1, 1, // v0-v3-v4-v5 right |
|
443 1, 0, 1, 1, 0, 1, 0, 0, // v0-v5-v6-v1 top |
|
444 1, 1, 0, 1, 0, 0, 1, 0, // v1-v6-v7-v2 left |
|
445 0, 0, 1, 0, 1, 1, 0, 1, // v7-v4-v3-v2 bottom |
|
446 0, 0, 1, 0, 1, 1, 0, 1 ] // v4-v7-v6-v5 back |
|
447 ); |
|
448 |
|
449 // index array |
|
450 var indices = new Uint8Array( |
|
451 [ 0, 1, 2, 0, 2, 3, // front |
|
452 4, 5, 6, 4, 6, 7, // right |
|
453 8, 9,10, 8,10,11, // top |
|
454 12,13,14, 12,14,15, // left |
|
455 16,17,18, 16,18,19, // bottom |
|
456 20,21,22, 20,22,23 ] // back |
|
457 ); |
|
458 |
|
459 var retval = { }; |
|
460 |
|
461 retval.normalObject = ctx.createBuffer(); |
|
462 ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject); |
|
463 ctx.bufferData(ctx.ARRAY_BUFFER, normals, ctx.STATIC_DRAW); |
|
464 |
|
465 retval.texCoordObject = ctx.createBuffer(); |
|
466 ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject); |
|
467 ctx.bufferData(ctx.ARRAY_BUFFER, texCoords, ctx.STATIC_DRAW); |
|
468 |
|
469 retval.vertexObject = ctx.createBuffer(); |
|
470 ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject); |
|
471 ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW); |
|
472 |
|
473 ctx.bindBuffer(ctx.ARRAY_BUFFER, 0); |
|
474 |
|
475 retval.indexObject = ctx.createBuffer(); |
|
476 ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject); |
|
477 ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, indices, ctx.STATIC_DRAW); |
|
478 ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, 0); |
|
479 |
|
480 retval.numIndices = indices.length; |
|
481 |
|
482 return retval; |
|
483 } |
|
484 |
|
485 // |
|
486 // makeSphere |
|
487 // |
|
488 // Create a sphere with the passed number of latitude and longitude bands and the passed radius. |
|
489 // Sphere has vertices, normals and texCoords. Create VBOs for each as well as the index array. |
|
490 // Return an object with the following properties: |
|
491 // |
|
492 // normalObject WebGLBuffer object for normals |
|
493 // texCoordObject WebGLBuffer object for texCoords |
|
494 // vertexObject WebGLBuffer object for vertices |
|
495 // indexObject WebGLBuffer object for indices |
|
496 // numIndices The number of indices in the indexObject |
|
497 // |
|
498 function makeSphere(ctx, radius, lats, longs) |
|
499 { |
|
500 var geometryData = [ ]; |
|
501 var normalData = [ ]; |
|
502 var texCoordData = [ ]; |
|
503 var indexData = [ ]; |
|
504 |
|
505 for (var latNumber = 0; latNumber <= lats; ++latNumber) { |
|
506 for (var longNumber = 0; longNumber <= longs; ++longNumber) { |
|
507 var theta = latNumber * Math.PI / lats; |
|
508 var phi = longNumber * 2 * Math.PI / longs; |
|
509 var sinTheta = Math.sin(theta); |
|
510 var sinPhi = Math.sin(phi); |
|
511 var cosTheta = Math.cos(theta); |
|
512 var cosPhi = Math.cos(phi); |
|
513 |
|
514 var x = cosPhi * sinTheta; |
|
515 var y = cosTheta; |
|
516 var z = sinPhi * sinTheta; |
|
517 var u = 1-(longNumber/longs); |
|
518 var v = latNumber/lats; |
|
519 |
|
520 normalData.push(x); |
|
521 normalData.push(y); |
|
522 normalData.push(z); |
|
523 texCoordData.push(u); |
|
524 texCoordData.push(v); |
|
525 geometryData.push(radius * x); |
|
526 geometryData.push(radius * y); |
|
527 geometryData.push(radius * z); |
|
528 } |
|
529 } |
|
530 |
|
531 longs += 1; |
|
532 for (var latNumber = 0; latNumber < lats; ++latNumber) { |
|
533 for (var longNumber = 0; longNumber < longs; ++longNumber) { |
|
534 var first = (latNumber * longs) + (longNumber % longs); |
|
535 var second = first + longs; |
|
536 indexData.push(first); |
|
537 indexData.push(second); |
|
538 indexData.push(first+1); |
|
539 |
|
540 indexData.push(second); |
|
541 indexData.push(second+1); |
|
542 indexData.push(first+1); |
|
543 } |
|
544 } |
|
545 |
|
546 var retval = { }; |
|
547 |
|
548 retval.normalObject = ctx.createBuffer(); |
|
549 ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject); |
|
550 ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(normalData), ctx.STATIC_DRAW); |
|
551 |
|
552 retval.texCoordObject = ctx.createBuffer(); |
|
553 ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject); |
|
554 ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(texCoordData), ctx.STATIC_DRAW); |
|
555 |
|
556 retval.vertexObject = ctx.createBuffer(); |
|
557 ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject); |
|
558 ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(geometryData), ctx.STATIC_DRAW); |
|
559 |
|
560 retval.numIndices = indexData.length; |
|
561 retval.indexObject = ctx.createBuffer(); |
|
562 ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject); |
|
563 ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexData), ctx.STREAM_DRAW); |
|
564 |
|
565 return retval; |
|
566 } |
|
567 |
|
568 // |
|
569 // loadObj |
|
570 // |
|
571 // Load a .obj file from the passed URL. Return an object with a 'loaded' property set to false. |
|
572 // When the object load is complete, the 'loaded' property becomes true and the following |
|
573 // properties are set: |
|
574 // |
|
575 // normalObject WebGLBuffer object for normals |
|
576 // texCoordObject WebGLBuffer object for texCoords |
|
577 // vertexObject WebGLBuffer object for vertices |
|
578 // indexObject WebGLBuffer object for indices |
|
579 // numIndices The number of indices in the indexObject |
|
580 // |
|
581 function loadObj(ctx, url) |
|
582 { |
|
583 var obj = { loaded : false }; |
|
584 obj.ctx = ctx; |
|
585 var req = new XMLHttpRequest(); |
|
586 req.obj = obj; |
|
587 req.onreadystatechange = function () { processLoadObj(req) }; |
|
588 req.open("GET", url, true); |
|
589 req.send(null); |
|
590 return obj; |
|
591 } |
|
592 |
|
593 function processLoadObj(req) |
|
594 { |
|
595 webglTestLog("req="+req) |
|
596 // only if req shows "complete" |
|
597 if (req.readyState == 4) { |
|
598 doLoadObj(req.obj, req.responseText); |
|
599 } |
|
600 } |
|
601 |
|
602 function doLoadObj(obj, text) |
|
603 { |
|
604 vertexArray = [ ]; |
|
605 normalArray = [ ]; |
|
606 textureArray = [ ]; |
|
607 indexArray = [ ]; |
|
608 |
|
609 var vertex = [ ]; |
|
610 var normal = [ ]; |
|
611 var texture = [ ]; |
|
612 var facemap = { }; |
|
613 var index = 0; |
|
614 |
|
615 var lines = text.split("\n"); |
|
616 for (var lineIndex in lines) { |
|
617 var line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, ""); |
|
618 |
|
619 // ignore comments |
|
620 if (line[0] == "#") |
|
621 continue; |
|
622 |
|
623 var array = line.split(" "); |
|
624 if (array[0] == "v") { |
|
625 // vertex |
|
626 vertex.push(parseFloat(array[1])); |
|
627 vertex.push(parseFloat(array[2])); |
|
628 vertex.push(parseFloat(array[3])); |
|
629 } |
|
630 else if (array[0] == "vt") { |
|
631 // normal |
|
632 texture.push(parseFloat(array[1])); |
|
633 texture.push(parseFloat(array[2])); |
|
634 } |
|
635 else if (array[0] == "vn") { |
|
636 // normal |
|
637 normal.push(parseFloat(array[1])); |
|
638 normal.push(parseFloat(array[2])); |
|
639 normal.push(parseFloat(array[3])); |
|
640 } |
|
641 else if (array[0] == "f") { |
|
642 // face |
|
643 if (array.length != 4) { |
|
644 webglTestLog("*** Error: face '"+line+"' not handled"); |
|
645 continue; |
|
646 } |
|
647 |
|
648 for (var i = 1; i < 4; ++i) { |
|
649 if (!(array[i] in facemap)) { |
|
650 // add a new entry to the map and arrays |
|
651 var f = array[i].split("/"); |
|
652 var vtx, nor, tex; |
|
653 |
|
654 if (f.length == 1) { |
|
655 vtx = parseInt(f[0]) - 1; |
|
656 nor = vtx; |
|
657 tex = vtx; |
|
658 } |
|
659 else if (f.length = 3) { |
|
660 vtx = parseInt(f[0]) - 1; |
|
661 tex = parseInt(f[1]) - 1; |
|
662 nor = parseInt(f[2]) - 1; |
|
663 } |
|
664 else { |
|
665 webglTestLog("*** Error: did not understand face '"+array[i]+"'"); |
|
666 return null; |
|
667 } |
|
668 |
|
669 // do the vertices |
|
670 var x = 0; |
|
671 var y = 0; |
|
672 var z = 0; |
|
673 if (vtx * 3 + 2 < vertex.length) { |
|
674 x = vertex[vtx*3]; |
|
675 y = vertex[vtx*3+1]; |
|
676 z = vertex[vtx*3+2]; |
|
677 } |
|
678 vertexArray.push(x); |
|
679 vertexArray.push(y); |
|
680 vertexArray.push(z); |
|
681 |
|
682 // do the textures |
|
683 x = 0; |
|
684 y = 0; |
|
685 if (tex * 2 + 1 < texture.length) { |
|
686 x = texture[tex*2]; |
|
687 y = texture[tex*2+1]; |
|
688 } |
|
689 textureArray.push(x); |
|
690 textureArray.push(y); |
|
691 |
|
692 // do the normals |
|
693 x = 0; |
|
694 y = 0; |
|
695 z = 1; |
|
696 if (nor * 3 + 2 < normal.length) { |
|
697 x = normal[nor*3]; |
|
698 y = normal[nor*3+1]; |
|
699 z = normal[nor*3+2]; |
|
700 } |
|
701 normalArray.push(x); |
|
702 normalArray.push(y); |
|
703 normalArray.push(z); |
|
704 |
|
705 facemap[array[i]] = index++; |
|
706 } |
|
707 |
|
708 indexArray.push(facemap[array[i]]); |
|
709 } |
|
710 } |
|
711 } |
|
712 |
|
713 // set the VBOs |
|
714 obj.normalObject = obj.ctx.createBuffer(); |
|
715 obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.normalObject); |
|
716 obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(normalArray), obj.ctx.STATIC_DRAW); |
|
717 |
|
718 obj.texCoordObject = obj.ctx.createBuffer(); |
|
719 obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.texCoordObject); |
|
720 obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(textureArray), obj.ctx.STATIC_DRAW); |
|
721 |
|
722 obj.vertexObject = obj.ctx.createBuffer(); |
|
723 obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.vertexObject); |
|
724 obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(vertexArray), obj.ctx.STATIC_DRAW); |
|
725 |
|
726 obj.numIndices = indexArray.length; |
|
727 obj.indexObject = obj.ctx.createBuffer(); |
|
728 obj.ctx.bindBuffer(obj.ctx.ELEMENT_ARRAY_BUFFER, obj.indexObject); |
|
729 obj.ctx.bufferData(obj.ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexArray), obj.ctx.STREAM_DRAW); |
|
730 |
|
731 obj.loaded = true; |
|
732 } |
|
733 |
|
734 // |
|
735 // loadImageTexture |
|
736 // |
|
737 // Load the image at the passed url, place it in a new WebGLTexture object and return the WebGLTexture. |
|
738 // |
|
739 function loadImageTexture(ctx, url) |
|
740 { |
|
741 var texture = ctx.createTexture(); |
|
742 texture.image = new Image(); |
|
743 texture.image.onload = function() { doLoadImageTexture(ctx, texture.image, texture) } |
|
744 texture.image.src = url; |
|
745 return texture; |
|
746 } |
|
747 |
|
748 function doLoadImageTexture(ctx, image, texture) |
|
749 { |
|
750 ctx.enable(ctx.TEXTURE_2D); |
|
751 ctx.bindTexture(ctx.TEXTURE_2D, texture); |
|
752 ctx.texImage2D(ctx.TEXTURE_2D, 0, ctx.RGBA, ctx.RGBA, ctx.UNSIGNED_BYTE, image); |
|
753 ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.LINEAR); |
|
754 ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.LINEAR_MIPMAP_LINEAR); |
|
755 ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE); |
|
756 ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE); |
|
757 ctx.generateMipmap(ctx.TEXTURE_2D) |
|
758 ctx.bindTexture(ctx.TEXTURE_2D, 0); |
|
759 } |
|
760 |
|
761 // |
|
762 // Framerate object |
|
763 // |
|
764 // This object keeps track of framerate and displays it as the innerHTML text of the |
|
765 // HTML element with the passed id. Once created you call snapshot at the end |
|
766 // of every rendering cycle. Every 500ms the framerate is updated in the HTML element. |
|
767 // |
|
768 Framerate = function(id) |
|
769 { |
|
770 this.numFramerates = 10; |
|
771 this.framerateUpdateInterval = 500; |
|
772 this.id = id; |
|
773 |
|
774 this.renderTime = -1; |
|
775 this.framerates = [ ]; |
|
776 self = this; |
|
777 var fr = function() { self.updateFramerate() } |
|
778 setInterval(fr, this.framerateUpdateInterval); |
|
779 } |
|
780 |
|
781 Framerate.prototype.updateFramerate = function() |
|
782 { |
|
783 var tot = 0; |
|
784 for (var i = 0; i < this.framerates.length; ++i) |
|
785 tot += this.framerates[i]; |
|
786 |
|
787 var framerate = tot / this.framerates.length; |
|
788 framerate = Math.round(framerate); |
|
789 document.getElementById(this.id).innerHTML = "Framerate:"+framerate+"fps"; |
|
790 } |
|
791 |
|
792 Framerate.prototype.snapshot = function() |
|
793 { |
|
794 if (this.renderTime < 0) |
|
795 this.renderTime = new Date().getTime(); |
|
796 else { |
|
797 var newTime = new Date().getTime(); |
|
798 var t = newTime - this.renderTime; |
|
799 var framerate = 1000/t; |
|
800 this.framerates.push(framerate); |
|
801 while (this.framerates.length > this.numFramerates) |
|
802 this.framerates.shift(); |
|
803 this.renderTime = newTime; |
|
804 } |
|
805 } |