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