1 /**
  2 	@preserve Copyright (c) 2012 Humu humu2009@gmail.com
  3 	jsc3d is freely distributable under the terms of the MIT license.
  4 
  5 	Permission is hereby granted, free of charge, to any person obtaining a copy
  6 	of this software and associated documentation files (the "Software"), to deal
  7 	in the Software without restriction, including without limitation the rights
  8 	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 	copies of the Software, and to permit persons to whom the Software is
 10 	furnished to do so, subject to the following conditions:
 11 
 12 	The above copyright notice and this permission notice shall be included in
 13 	all copies or substantial portions of the Software.
 14 
 15 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17 	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18 	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19 	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20 	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21 	THE SOFTWARE.
 22 **/
 23 
 24 
 25 /**
 26 	@namespace JSC3D
 27  */
 28 var JSC3D = JSC3D || {};
 29 
 30 
 31 /**
 32 	@class Viewer
 33 
 34 	Viewer is the main class of JSC3D. It provides presentation of and interaction with a simple static 3D scene 
 35 	which can either be given as the url of the scene file, or be manually constructed and passed in. It 
 36 	also provides some settings to adjust the mode and quality of the rendering.<br /><br />
 37 
 38 	Viewer should be constructed with an existing canvas object where to perform the rendering.<br /><br />
 39 
 40 	Viewer provides 3 way to specify the scene:<br />
 41 	1. Use setParameter() method before initilization and set 'SceneUrl' parameter with a valid url  
 42 	   that describes where to load the scene. <br />
 43 	2. Use replaceSceneFromUrl() method, passing in a valid url to load/replace scene at runtime.<br />
 44 	3. Use replaceScene() method, passing in a manually constructed scene object to replace the current one 
 45 	   at runtime.<br />
 46  */
 47 JSC3D.Viewer = function(canvas, parameters) {
 48 	if(parameters)
 49 		this.params = {
 50 			SceneUrl:			parameters.SceneUrl || '', 
 51 			InitRotationX:		parameters.InitRotationX || 0, 
 52 			InitRotationY:		parameters.InitRotationY || 0, 
 53 			InitRotationZ:		parameters.InitRotationZ || 0, 
 54 			ModelColor:			parameters.ModelColor || '#caa618', 
 55 			BackgroundColor1:	parameters.BackgroundColor1 || '#ffffff', 
 56 			BackgroundColor2:	parameters.BackgroundColor2 || '#383840', 
 57 			BackgroundImageUrl:	parameters.BackgroundImageUrl || '', 
 58 			RenderMode:			parameters.RenderMode || 'flat', 
 59 			Definition:			parameters.Definition || 'standard', 
 60 			MipMapping:			parameters.MipMapping || 'off', 
 61 			SphereMapUrl:		parameters.SphereMapUrl || ''
 62 		};
 63 	else
 64 		this.params = {
 65 			SceneUrl: '', 
 66 			InitRotationX: 0, 
 67 			InitRotationY: 0, 
 68 			InitRotationZ: 0, 
 69 			ModelColor: '#caa618', 
 70 			BackgroundColor1: '#ffffff', 
 71 			BackgroundColor2: '#383840', 
 72 			BackgroundImageUrl: '', 
 73 			RenderMode: 'flat', 
 74 			Definition: 'standard', 
 75 			MipMapping: 'off', 
 76 			SphereMapUrl: ''
 77 		};
 78 
 79 	this.canvas = canvas;
 80 	this.ctx = null;
 81 	this.canvasData = null;
 82 	this.bkgColorBuffer = null;
 83 	this.colorBuffer = null;
 84 	this.zBuffer = null;
 85 	this.selectionBuffer = null;
 86 	this.frameWidth = canvas.width;
 87 	this.frameHeight = canvas.height;
 88 	this.scene = null;
 89 	this.defaultMaterial = null;
 90 	this.sphereMap = null;
 91 	this.isLoaded = false;
 92 	this.isFailed = false;
 93 	this.errorMsg = '';
 94 	this.needUpdate = false;
 95 	this.needRepaint = false;
 96 	this.initRotX = 0;
 97 	this.initRotY = 0;
 98 	this.initRotZ = 0;
 99 	this.zoomFactor = 1;
100 	this.rotMatrix = new JSC3D.Matrix3x4;
101 	this.transformMatrix = new JSC3D.Matrix3x4;
102 	this.sceneUrl = '';
103 	this.modelColor = 0xcaa618;
104 	this.bkgColor1 = 0xffffff;
105 	this.bkgColor2 = 0x383840;
106 	this.bkgImageUrl = '';
107 	this.bkgImage = null;
108 	this.renderMode = 'flat';
109 	this.definition = 'standard';
110 	this.isMipMappingOn = false;
111 	this.sphereMapUrl = '';
112 	this.buttonStates = {};
113 	this.keyStates = {};
114 	this.mouseX = 0;
115 	this.mouseY = 0;
116 	this.onmousedown = null;
117 	this.onmouseup = null;
118 	this.onmousemove = null;
119 	this.beforeupdate = null;
120 	this.afterupdate = null;
121 	this.mouseUsage = 'default';
122 	this.isDefaultInputHandlerEnabled = true;
123 
124 	// setup input handlers.
125 	// compatibility for touch devices is taken into account
126 	var isTouchDevice = (document.createTouch != undefined);	// detect if it is running on a touch device
127 	var self = this;
128 	if(!isTouchDevice) {
129 		this.canvas.addEventListener('mousedown', function(e){self.mouseDownHandler(e);}, false);
130 		this.canvas.addEventListener('mouseup', function(e){self.mouseUpHandler(e);}, false);
131 		this.canvas.addEventListener('mousemove', function(e){self.mouseMoveHandler(e);}, false);
132 		document.addEventListener('keydown', function(e){self.keyDownHandler(e);}, false);
133 		document.addEventListener('keyup', function(e){self.keyUpHandler(e);}, false);
134 	}
135 	else {
136 		this.canvas.addEventListener('touchstart', function(e){self.touchStartHandler(e);}, false);
137 		this.canvas.addEventListener('touchend', function(e){self.touchEndHandler(e);}, false);
138 		this.canvas.addEventListener('touchmove', function(e){self.touchMoveHandler(e);}, false);
139 	}
140 };
141 
142 /**
143 	Set the initial value for a parameter to parameterize the viewer.<br />
144 	Available parameters are:<br />
145 	'<b>SceneUrl</b>':				url string that describes where to load the scene, default to '';<br />
146 	'<b>InitRotationX</b>':			initial rotation angle around x-axis for the whole scene, default to 0;<br />
147 	'<b>InitRotationY</b>':			initial rotation angle around y-axis for the whole scene, default to 0;<br />
148 	'<b>InitRotationZ</b>':			initial rotation angle around z-axis for the whole scene, default to 0;<br />
149 	'<b>ModelColor</b>':			fallback color for all meshes, default to '#caa618';<br />
150 	'<b>BackgroundColor1</b>':		color at the top of the background, default to '#ffffff';<br />
151 	'<b>BackgroundColor2</b>':		color at the bottom of the background, default to '#383840';<br />
152 	'<b>BackgroundImageUrl</b>':	url string that describes where to load the image used for background, default to '';<br />
153 	'<b>RenderMode</b>':			render mode, default to 'flat';<br />
154 	'<b>Definition</b>':			quality level of rendering, default to 'standard';<br />
155 	'<b>MipMapping</b>':			turn on/off mip-mapping, default to 'off';<br />
156 	'<b>SphereMapUrl</b>':			url string that describes where to load the image used for sphere mapping, default to ''.<br />
157 	@param {String} name name of the parameter to set.
158 	@param value new value for the parameter.
159  */
160 JSC3D.Viewer.prototype.setParameter = function(name, value) {
161 	this.params[name] = value;
162 };
163 
164 /**
165 	Initialize viewer for rendering and interactions.
166  */
167 JSC3D.Viewer.prototype.init = function() {
168 	this.sceneUrl = this.params['SceneUrl'];
169 	this.initRotX = parseFloat(this.params['InitRotationX']);
170 	this.initRotY = parseFloat(this.params['InitRotationY']);
171 	this.initRotZ = parseFloat(this.params['InitRotationZ']);
172 	this.modelColor = parseInt('0x' + this.params['ModelColor'].substring(1));
173 	this.bkgColor1 = parseInt('0x' + this.params['BackgroundColor1'].substring(1));
174 	this.bkgColor2 = parseInt('0x' + this.params['BackgroundColor2'].substring(1));
175 	this.bkgImageUrl = this.params['BackgroundImageUrl'];
176 	this.renderMode = this.params['RenderMode'].toLowerCase();
177 	this.definition = this.params['Definition'].toLowerCase();
178 	this.isMipMappingOn = this.params['MipMapping'].toLowerCase() == 'on';
179 	this.sphereMapUrl = this.params['SphereMapUrl'];
180 
181 	try {
182 		this.ctx = this.canvas.getContext('2d');
183 		this.canvasData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
184 	}
185 	catch(e) {
186 		this.ctx = null;
187 		this.canvasData = null;
188 	}
189 
190 	if(this.canvas.width <= 2 || this.canvas.height <= 2)
191 		this.definition = 'standard';
192 	
193 	switch(this.definition) {
194 	case 'low':
195 		this.frameWidth = ~~((this.canvas.width + 1) / 2);
196 		this.frameHeight = ~~((this.canvas.height + 1) / 2);
197 		break;
198 	case 'high':
199 		this.frameWidth = this.canvas.width * 2;
200 		this.frameHeight = this.canvas.height * 2;
201 		break;
202 	case 'standard':
203 	default:
204 		this.frameWidth = this.canvas.width;
205 		this.frameHeight = this.canvas.height;
206 		break;
207 	}
208 
209 	this.zoomFactor = 1;
210 	this.rotMatrix.identity();
211 	this.transformMatrix.identity();
212 	this.isLoaded = false;
213 	this.isFailed = false;
214 	this.errorMsg = '';
215 	this.needUpdate = false;
216 	this.needRepaint = false;
217 	this.scene = null;
218 	// allocate memory storage for frame buffers
219 	this.colorBuffer = new Array(this.frameWidth * this.frameHeight);
220 	this.zBuffer = new Array(this.frameWidth * this.frameHeight);
221 	this.selectionBuffer = new Array(this.frameWidth * this.frameHeight);
222 	this.bkgColorBuffer = new Array(this.frameWidth * this.frameHeight);
223 	this.generateBackground();
224 	// create a default material for rendring of meshes that don't have one
225 	this.defaultMaterial = new JSC3D.Material;
226 	this.defaultMaterial.ambientColor = 0;
227 	this.defaultMaterial.diffuseColor = this.modelColor;
228 	this.defaultMaterial.transparency = 0;
229 	this.defaultMaterial.simulateSpecular = true;
230 	this.drawBackground();
231 
232 	// set a timer to wake up update routine per 30 milliseconds
233 	var self = this;
234 	setInterval( function(){self.doUpdate();}, 30 );
235 
236 	// load background image if any
237 	this.setBackgroudImageFromUrl(this.bkgImageUrl);
238 
239 	// load scene if any
240 	this.loadScene();
241 	
242 	// load sphere mapping image if any
243 	this.setSphereMapFromUrl(this.sphereMapUrl);
244 };
245 
246 /**
247 	Ask viewer to render a new frame or just repaint last frame.
248 	@param {Boolean} repaintOnly true to repaint last frame; false(default) to render a new frame.
249  */
250 JSC3D.Viewer.prototype.update = function(repaintOnly) {
251 	if(this.isFailed) {
252 		this.reportError(this.errorMsg);
253 		return;
254 	}
255 
256 	if(repaintOnly)
257 		this.needRepaint = true;
258 	else
259 		this.needUpdate = true;
260 };
261 
262 /**
263 	Rotate the scene with given angles around Cardinal axes.
264 	@param {Number} rotX rotation angle around X-axis in degrees.
265 	@param {Number} rotY rotation angle around Y-axis in degrees.
266 	@param {Number} rotZ rotation angle around Z-axis in degrees.
267  */
268 JSC3D.Viewer.prototype.rotate = function(rotX, rotY, rotZ) {
269 	this.rotMatrix.rotateAboutXAxis(rotX);
270 	this.rotMatrix.rotateAboutYAxis(rotY);
271 	this.rotMatrix.rotateAboutZAxis(rotZ);
272 };
273 
274 /**
275 	Set render mode.<br />
276 	Available render modes are:<br />
277 	'<b>point</b>':         render meshes as point clouds;<br />
278 	'<b>wireframe</b>':     render meshes as wireframe;<br />
279 	'<b>flat</b>':          render meshes as solid objects using flat shading;<br />
280 	'<b>smooth</b>':        render meshes as solid objects using smooth shading;<br />
281 	'<b>texture</b>':       render meshes as solid textured objects, no lighting will be apllied;<br />
282 	'<b>textureflat</b>':   render meshes as solid textured objects, lighting will be calculated per face;<br />
283 	'<b>texturesmooth</b>': render meshes as solid textured objects, lighting will be calculated per vertex and interpolated.<br />
284 	@param {String} mode new render mode.
285  */
286 JSC3D.Viewer.prototype.setRenderMode = function(mode) {
287 	this.params['RenderMode'] = mode;
288 	this.renderMode = mode;
289 };
290 
291 /**
292 	Set quality level of rendering.<br />
293 	Available quality levels are:<br />
294 	'<b>low</b>':      low-quality rendering will be applied, with highest performance;<br />
295 	'<b>standard</b>': normal-quality rendering will be applied, with modest performace;<br />
296 	'<b>high</b>':     high-quality rendering will be applied, with lowest performace.<br />
297 	@params {String} definition new quality level.
298  */
299 JSC3D.Viewer.prototype.setDefinition = function(definition) {
300 	if(this.canvas.width <= 2 || this.canvas.height <= 2)
301 		definition = 'standard';
302 
303 	if(definition == this.definition)
304 		return;
305 	
306 	this.params['Definition'] = definition;
307 	this.definition = definition;
308 
309 	var oldFrameWidth = this.frameWidth;
310 
311 	switch(this.definition) {
312 	case 'low':
313 		this.frameWidth = ~~((this.canvas.width + 1) / 2);
314 		this.frameHeight = ~~((this.canvas.height + 1) / 2);
315 		break;
316 	case 'high':
317 		this.frameWidth = this.canvas.width * 2;
318 		this.frameHeight = this.canvas.height * 2;
319 		break;
320 	case 'standard':
321 	default:
322 		this.frameWidth = this.canvas.width;
323 		this.frameHeight = this.canvas.height;
324 		break;
325 	}
326 
327 	var newSize = this.frameWidth * this.frameHeight;
328 	if(this.colorBuffer.length < newSize)
329 		this.colorBuffer = new Array(newSize);
330 
331 	if(this.zBuffer.length < newSize)
332 		this.zBuffer = new Array(newSize);
333 
334 	if(this.selectionBuffer.length < newSize)
335 		this.selectionBuffer = new Array(newSize);
336 
337 	if(this.bkgColorBuffer.length < newSize)
338 		this.bkgColorBuffer = new Array(newSize);
339 
340 	this.generateBackground();
341 
342 	// zoom factor should be adjusted, 
343 	// otherwise there would be an abrupt zoom-in or zoom-out on next frame
344 	this.zoomFactor *= this.frameWidth / oldFrameWidth;
345 };
346 
347 /**
348 	Specify the url for the background image.
349 	@param {String} backgroundImageUrl url string for the background image.
350  */
351 JSC3D.Viewer.prototype.setBackgroudImageFromUrl = function(backgroundImageUrl) {
352 	this.params['BackgroundImageUrl'] = backgroundImageUrl;
353 	this.bkgImageUrl = backgroundImageUrl;
354 
355 	if(backgroundImageUrl == '') {
356 		this.bkgImage = null;
357 		return;
358 	}
359 
360 	var self = this;
361 	var img = new Image;
362 
363 	img.onload = function() {
364 		self.bkgImage = this;
365 		self.generateBackground();
366 	};
367 
368 	img.src = backgroundImageUrl;
369 };
370 
371 /**
372 	Specify a new image from the given url which will be used for applying sphere mapping.
373 	@param {String} sphereMapUrl url string that describes where to load the image.
374  */
375 JSC3D.Viewer.prototype.setSphereMapFromUrl = function(sphereMapUrl) {
376 	this.params['SphereMapUrl'] = sphereMapUrl;
377 	this.sphereMapUrl = sphereMapUrl;
378 
379 	if(sphereMapUrl == '') {
380 		this.sphereMap = null;
381 		return;
382 	}
383 
384 	var self = this;
385 	var newSphereMap = new JSC3D.Texture;
386 
387 	newSphereMap.onready = function() {
388 		self.sphereMap = newSphereMap;
389 		self.update();
390 	};
391 
392 	newSphereMap.createFromUrl(this.sphereMapUrl);
393 };
394 
395 /**
396 	Enable/Disable the default mouse and key event handling routines.
397 	@param {Boolean} enabled true to enable the default handler; false to disable them.
398  */
399 JSC3D.Viewer.prototype.enableDefaultInputHandler = function(enabled) {
400 	this.isDefaultInputHandlerEnabled = enabled;
401 };
402 
403 /**
404 	Set control of mouse pointer.
405 	Available options are:<br />
406 	'<b>default</b>':	default mouse control will be used;<br />
407 	'<b>free</b>':		this tells {JSC3D.Viewer} a user-defined mouse control will be adopted. 
408 						This is often used together with viewer.enableDefaultInputHandler(false) 
409 						and viewer.onmousedown, viewer.onmouseup and/or viewer.onmousemove overridden.<br />
410 	'<b>rotate</b>':	mouse will be used to rotate the scene;<br />
411 	'<b>zoom</b>':		mouse will be used to do zooming.<br />
412 	@param {String} usage control of mouse pointer to be set.
413  */
414 JSC3D.Viewer.prototype.setMouseUsage = function(usage) {
415 	this.mouseUsage = usage;
416 };
417 
418 /**
419 	Load a new scene from the given url to replace the current scene.
420 	@param {String} sceneUrl url string that describes where to load the new scene.
421  */
422 JSC3D.Viewer.prototype.replaceSceneFromUrl = function(sceneUrl) {
423 	this.params['SceneUrl'] = sceneUrl;
424 	this.sceneUrl = sceneUrl;
425 	this.isFailed = this.isLoaded = false;
426 	this.loadScene();
427 };
428 
429 /**
430 	Replace the current scene with a given scene.
431 	@param {JSC3D.Scene} scene the given scene.
432  */
433 JSC3D.Viewer.prototype.replaceScene = function(scene) {
434 	this.params['SceneUrl'] = '';
435 	this.sceneUrl = '';
436 	this.isFailed = false;
437 	this.isLoaded = true;
438 	this.errorMsg = '';
439 	this.setupScene(scene);
440 };
441 
442 /**
443 	Get the current scene.
444 	@returns {JSC3D.Scene} the current scene.
445  */
446 JSC3D.Viewer.prototype.getScene = function() {
447 	return this.scene;
448 };
449 
450 /**
451 	Query information at a given position on the canvas.
452 	@param {Number} clientX client x coordinate on the current page.
453 	@param {Number} clientY client y coordinate on the current page.
454 	@returns {JSC3D.PickInfo} a PickInfo object which holds the result.
455  */
456 JSC3D.Viewer.prototype.pick = function(clientX, clientY) {
457 	var pickInfo = new JSC3D.PickInfo;
458 
459 	var canvasRect = this.canvas.getBoundingClientRect();
460 	var canvasX = clientX - canvasRect.left;
461 	var canvasY = clientY - canvasRect.top;
462 	
463 	var frameX = canvasX;
464 	var frameY = canvasY;
465 	if( this.selectionBuffer != null && 
466 		canvasX >= 0 && canvasX < this.canvas.width && 
467 		canvasY >= 0 && canvasY < this.canvas.height ) {
468 		switch(this.definition) {
469 		case 'low':
470 			frameX = ~~(frameX / 2);
471 			frameY = ~~(frameY / 2);
472 			break;
473 		case 'high':
474 			frameX *= 2;
475 			frameY *= 2;
476 			break;
477 		case 'standard':
478 		default:
479 			break;
480 		}
481 
482 		var pickedId = this.selectionBuffer[frameY * this.frameWidth + frameX];
483 		if(pickedId > 0) {
484 			var meshes = this.scene.getChildren();
485 			for(var i=0; i<meshes.length; i++) {
486 				if(meshes[i].internalId == pickedId) {
487 					pickInfo.mesh = meshes[i];
488 					break;
489 				}
490 			}
491 		}
492 	}
493 
494 	pickInfo.canvasX = canvasX;
495 	pickInfo.canvasY = canvasY;
496 	if(pickInfo.mesh)
497 		pickInfo.depth = this.zBuffer[frameY * this.frameWidth + frameX];
498 
499 	return pickInfo;
500 };
501 
502 /**
503 	Render a new frame or repaint last frame.
504 	@private
505  */
506 JSC3D.Viewer.prototype.doUpdate = function() {
507 	if(this.needUpdate || this.needRepaint) {
508 		if(this.beforeupdate != null && (typeof this.beforeupdate) == 'function')
509 			this.beforeupdate();
510 
511 		if(this.scene) {
512 			if(this.needUpdate && this.colorBuffer != null) {
513 				this.beginScene();
514 				this.render();
515 				this.endScene();
516 			}
517 
518 			this.paint();
519 		}
520 		else {
521 			this.drawBackground();
522 		}
523 
524 		this.needRepaint = false;
525 		this.needUpdate = false;
526 
527 		if(this.afterupdate != null && (typeof this.afterupdate) == 'function')
528 			this.afterupdate();
529 	}
530 };
531 
532 /**
533 	Paint onto canvas.
534 	@private
535  */
536 JSC3D.Viewer.prototype.paint = function() {
537 	if(!this.canvasData)
538 		return;
539 
540 	this.ctx.putImageData(this.canvasData, 0, 0);
541 };
542 
543 /**
544 	The mouseDown event handling routine.
545 	@private
546  */
547 JSC3D.Viewer.prototype.mouseDownHandler = function(e) {
548 	if(this.onmousedown) {
549 		var info = this.pick(e.clientX, e.clientY);
550 		this.onmousedown(info.canvasX, info.canvasY, e.button, info.depth, info.mesh);
551 	}
552 
553 	e.preventDefault();
554 
555 	if(!this.isDefaultInputHandlerEnabled)
556 		return;
557 
558 	this.buttonStates[e.button] = true;
559 	this.mouseX = e.clientX;
560 	this.mouseY = e.clientY;
561 };
562 
563 /**
564 	The mouseUp event handling routine.
565 	@private
566  */
567 JSC3D.Viewer.prototype.mouseUpHandler = function(e) {
568 	if(this.onmouseup) {
569 		var info = this.pick(e.clientX, e.clientY);
570 		this.onmouseup(info.canvasX, info.canvasY, e.button, info.depth, info.mesh);
571 	}
572 
573 	e.preventDefault();
574 
575 	if(!this.isDefaultInputHandlerEnabled)
576 		return;
577 
578 	this.buttonStates[e.button] = false;
579 };
580 
581 /**
582 	The mouseMove event handling routine.
583 	@private
584  */
585 JSC3D.Viewer.prototype.mouseMoveHandler = function(e) {
586 	if(this.onmousemove) {
587 		var info = this.pick(e.clientX, e.clientY);
588 		this.onmousemove(info.canvasX, info.canvasY, e.button, info.depth, info.mesh);
589 	}
590 
591 	e.preventDefault();
592 
593 	if(!this.isDefaultInputHandlerEnabled)
594 		return;
595 
596 	var isDragging = this.buttonStates[0] == true;
597 	var isShiftDown = this.keyStates[16] == true;
598 	if(isDragging) {
599 		if((isShiftDown && this.mouseUsage == 'default') || this.mouseUsage == 'zoom') {
600 			this.zoomFactor *= this.mouseY <= e.clientY ? 1.11 : 0.9;
601 		}
602 		else if(this.mouseUsage == 'default' || this.mouseUsage == 'rotate') {
603 			var rotX = (e.clientY - this.mouseY) * 360 / this.canvas.width;
604 			var rotY = (e.clientX - this.mouseX) * 360 / this.canvas.height;
605 			this.rotMatrix.rotateAboutXAxis(rotX);
606 			this.rotMatrix.rotateAboutYAxis(rotY);
607 		}
608 		this.mouseX = e.clientX;
609 		this.mouseY = e.clientY;
610 		this.update();
611 	}
612 };
613 
614 /**
615 	The touchStart event handling routine. This is for compatibility for touch devices.
616 	@private
617  */
618 JSC3D.Viewer.prototype.touchStartHandler = function(e) {
619 	if(e.touches.length > 0) {
620 		var clientX = e.touches[0].clientX;
621 		var clientY = e.touches[0].clientY;
622 
623 		if(this.onmousedown) {
624 			var info = this.pick(clientX, clientY);
625 			this.onmousedown(info.canvasX, info.canvasY, 0, info.depth, info.mesh);
626 		}
627 
628 		e.preventDefault();
629 
630 		if(!this.isDefaultInputHandlerEnabled)
631 			return;
632 
633 		this.buttonStates[0] = true;
634 		this.mouseX = clientX;
635 		this.mouseY = clientY;
636 	}
637 };
638 
639 /**
640 	The touchEnd event handling routine. This is for compatibility for touch devices.
641 	@private
642  */
643 JSC3D.Viewer.prototype.touchEndHandler = function(e) {
644 	if(this.onmouseup) {
645 		var info = this.pick(this.mouseX, this.mouseY);
646 		this.onmouseup(info.canvasX, info.canvasY, 0, info.depth, info.mesh);
647 	}
648 
649 	e.preventDefault();
650 
651 	if(!this.isDefaultInputHandlerEnabled)
652 		return;
653 
654 	this.buttonStates[0] = false;
655 };
656 
657 /**
658 	The touchMove event handling routine. This is for compatibility for touch devices.
659 	@private
660  */
661 JSC3D.Viewer.prototype.touchMoveHandler = function(e) {
662 	if(e.touches.length > 0) {
663 		var clientX = e.touches[0].clientX;
664 		var clientY = e.touches[0].clientY;
665 
666 		if(this.onmousemove) {
667 			var info = this.pick(clientX, clientY);
668 			this.onmousemove(info.canvasX, info.canvasY, 0, info.depth, info.mesh);
669 		}
670 
671 		e.preventDefault();
672 
673 		if(!this.isDefaultInputHandlerEnabled)
674 			return;
675 
676 		if(this.mouseUsage == 'zoom') {
677 			this.zoomFactor *= (this.mouseY <= clientY) ? 1.11 : 0.9;
678 		}
679 		else if(this.mouseUsage == 'default' || this.mouseUsage == 'rotate') {
680 			var rotX = (clientY - this.mouseY) * 360 / this.canvas.width;
681 			var rotY = (clientX - this.mouseX) * 360 / this.canvas.height;
682 			this.rotMatrix.rotateAboutXAxis(rotX);
683 			this.rotMatrix.rotateAboutYAxis(rotY);
684 		}
685 		this.mouseX = clientX;
686 		this.mouseY = clientY;
687 
688 		this.update();
689 	}
690 };
691 
692 /**
693 	The keyDown event handling routine.
694 	@private
695  */
696 JSC3D.Viewer.prototype.keyDownHandler = function(e) {
697 	if(!this.isDefaultInputHandlerEnabled)
698 		return;
699 
700 	this.keyStates[e.keyCode] = true;
701 };
702 
703 /**
704 	The keyUp event handling routine.
705 	@private
706  */
707 JSC3D.Viewer.prototype.keyUpHandler = function(e) {
708 	if(!this.isDefaultInputHandlerEnabled)
709 		return;
710 
711 	this.keyStates[e.keyCode] = false;
712 };
713 
714 /**
715 	Internally load a scene.
716 	@private
717  */
718 JSC3D.Viewer.prototype.loadScene = function() {
719 	this.scene = null;
720 	this.isLoaded = false;
721 
722 	if(this.sceneUrl == '')
723 		return false;
724 
725 	var lastSlashAt = this.sceneUrl.lastIndexOf('/');
726 	if(lastSlashAt == -1)
727 		lastSlashAt = this.sceneUrl.lastIndexOf('\\');
728 	
729 	var fileName = this.sceneUrl.substring(lastSlashAt + 1);
730 	var lastDotAt = fileName.lastIndexOf('.');
731 	if(lastDotAt == -1)
732 		return false;
733 
734 	var fileExtName = fileName.substring(lastDotAt + 1);
735 	var loader = JSC3D.LoaderSelector.getLoader(fileExtName);
736 	if(!loader)
737 		return false;
738 
739 	var self = this;
740 
741 	loader.onload = function(scene) {
742 		self.setupScene(scene);
743 	};
744 
745 	loader.onerror = function(errorMsg) {
746 		self.scene = null;
747 		self.isLoaded = false;
748 		self.isFailed = true;
749 		self.errorMsg = errorMsg;
750 		self.update();
751 	};
752 
753 	loader.onprogress = function(task, prog) {
754 		self.reportProgress(task, prog);
755 	};
756 
757 	loader.onresource = function(resource) {
758 		if((resource instanceof JSC3D.Texture) && self.isMipMappingOn && !resource.hasMipmap())
759 			resource.generateMipmaps();		
760 		self.update();
761 	};
762 
763 	loader.loadFromUrl(this.sceneUrl);
764 
765 	return true;
766 };
767 
768 /**
769 	Prepare for rendering of a new scene.
770 	@private
771  */
772 JSC3D.Viewer.prototype.setupScene = function(scene) {
773 	scene.init();
774 	if(!scene.isEmpty()) {
775 		var d = scene.aabb.lengthOfDiagonal();
776 		var w = this.frameWidth;
777 		var h = this.frameHeight;
778 		this.zoomFactor = (d == 0) ? 1 : (w < h ? w : h) / d;
779 	}
780 
781 	this.rotMatrix.identity();
782 	this.rotMatrix.rotateAboutXAxis(this.initRotX);
783 	this.rotMatrix.rotateAboutYAxis(this.initRotY);
784 	this.rotMatrix.rotateAboutZAxis(this.initRotZ);
785 	this.scene = scene;
786 	this.isLoaded = true;
787 	this.isFailed = false;
788 	this.errorMsg = '';
789 	this.needUpdate = false;
790 	this.needRepaint = false;
791 	this.update();
792 };
793 
794 /**
795 	Show progress and some informations about current time-cosuming task.
796 	@param {String} task text information about current task.
797 	@param {Number} progress progress of current task. this should be a number between 0 and 1.
798  */
799 JSC3D.Viewer.prototype.reportProgress = function(task, progress) {
800 	if(!this.ctx)
801 		return;
802 
803 	this.drawBackground();
804 
805 	this.ctx.save();
806 
807 	var r = 255 - ((this.bkgColor1 & 0xff0000) >> 16);
808 	var g = 255 - ((this.bkgColor1 & 0xff00) >> 8);
809 	var b = 255 - (this.bkgColor1 & 0xff);
810 	var style = '#' + r.toString(16) + g.toString(16) + b.toString(16);
811 	this.ctx.strokeStyle = style;
812 	this.ctx.fillStyle = style;
813 
814 	var barX = 40;
815 	var barY = this.canvas.height * 0.38;
816 	var barWidth = this.canvas.width - barX * 2;
817 	var barHeight = 20;
818 	this.ctx.strokeRect(barX, barY, barWidth, barHeight);
819 	this.ctx.fillRect(barX+2, barY+2, (barWidth-4)*progress, barHeight-4);
820 
821 	this.ctx.font = '12px Courier New';
822 	this.ctx.textAlign = 'left';
823 	this.ctx.fillText(task, barX, barY-4, barWidth);
824 
825 	this.ctx.restore();
826 };
827 
828 /**
829 	Show informations about a fatal error.
830 	@param {String} message text information about this error.
831  */
832 JSC3D.Viewer.prototype.reportError = function(message) {
833 	if(!this.ctx)
834 		return;
835 
836 	this.drawBackground();
837 
838 	this.ctx.save();
839 
840 	var msgX = 40;
841 	var msgY = this.canvas.height * 0.38 - 4;
842 	var r = 255 - ((this.bkgColor1 & 0xff0000) >> 16);
843 	var g = 255 - ((this.bkgColor1 & 0xff00) >> 8);
844 	var b = 255 - (this.bkgColor1 & 0xff);
845 	var style = '#' + r.toString(16) + g.toString(16) + b.toString(16);
846 	this.ctx.fillStyle = style;
847 	this.ctx.font = '16px Courier New';
848 	this.ctx.textAlign = 'left';
849 	this.ctx.fillText(message, msgX, msgY);
850 
851 	this.ctx.restore();
852 };
853 
854 /**
855 	Fill the background color buffer.
856 	@private
857  */
858 JSC3D.Viewer.prototype.generateBackground = function() {
859 	if(this.bkgImage) {
860 		this.fillBackgroundWithImage();
861 	}
862 	else {
863 		this.fillGradientBackground();
864 	}
865 };
866 
867 /**
868 	Do fill the background color buffer with gradient colors.
869 	@private
870  */
871 JSC3D.Viewer.prototype.fillGradientBackground = function() {
872 	var w = this.frameWidth;
873 	var h = this.frameHeight;
874 	var pixels = this.bkgColorBuffer;
875 
876 	var r1 = (this.bkgColor1 & 0xff0000) >> 16;
877 	var g1 = (this.bkgColor1 & 0xff00) >> 8;
878 	var b1 = this.bkgColor1 & 0xff;
879 	var r2 = (this.bkgColor2 & 0xff0000) >> 16;
880 	var g2 = (this.bkgColor2 & 0xff00) >> 8;
881 	var b2 = this.bkgColor2 & 0xff;
882 
883 	var pix = 0;
884 	for(var i=0; i<h; i++) {
885 		var r = (r1 + i * (r2 - r1) / h) & 0xff;
886 		var g = (g1 + i * (g2 - g1) / h) & 0xff;
887 		var b = (b1 + i * (b2 - b1) / h) & 0xff;
888 
889 		for(var j=0; j<w; j++) {
890 			pixels[pix++] = r << 16 | g << 8 | b;
891 		}
892 	}
893 };
894 
895 /**
896 	Do fill the background color buffer with a loaded image.
897 	@private
898  */
899 JSC3D.Viewer.prototype.fillBackgroundWithImage = function() {
900 	var w = this.frameWidth;
901 	var h = this.frameHeight;	
902 	if(this.bkgImage.width <= 0 || this.bkgImage.height <= 0)
903 		return;
904 
905 	var isCanvasClean = false;
906 	var canvas = JSC3D.Texture.cv;
907 	if(!canvas) {
908 		try {
909 			canvas = document.createElement('canvas');
910 			JSC3D.Texture.cv = canvas;
911 			isCanvasClean = true;
912 		}
913 		catch(e) {
914 			return;
915 		}
916 	}
917 
918 	if(canvas.width != w || canvas.height != h) {
919 		canvas.width = w;
920 		canvas.height = h;
921 		isCanvasClean = true;
922 	}
923 
924 	var data = null;
925 	try {
926 		var ctx = canvas.getContext('2d');
927 		if(!isCanvasClean)
928 			ctx.clearRect(0, 0, w, h);
929 		ctx.drawImage(this.bkgImage, 0, 0, w, h);
930 		var imgData = ctx.getImageData(0, 0, w, h);
931 		data = imgData.data;
932 	}
933 	catch(e) {
934 		return;
935 	}
936 
937 	var pixels = this.bkgColorBuffer;
938 	var size = w * h;
939 	for(var i=0, j=0; i<size; i++, j+=4) {
940 		pixels[i] = data[j] << 16 | data[j+1] << 8 | data[j+2];
941 	}
942 };
943 
944 /**
945 	Draw background onto canvas.
946 	@private
947  */
948 JSC3D.Viewer.prototype.drawBackground = function() {
949 	if(!this.canvasData)
950 		return;
951 
952 	this.beginScene();
953 	this.endScene();
954 
955 	this.paint();
956 };
957 
958 /**
959 	Begin to render a new frame.
960 	@private
961  */
962 JSC3D.Viewer.prototype.beginScene = function() {
963 	var cbuf = this.colorBuffer;
964 	var zbuf = this.zBuffer;
965 	var sbuf = this.selectionBuffer;
966 	var bbuf = this.bkgColorBuffer;
967 	var size = this.frameWidth * this.frameHeight;
968 	var MIN_Z = -Number.MAX_VALUE;
969 
970 	for(var i=0; i<size; i++) {
971 		cbuf[i] = bbuf[i];
972 		zbuf[i] = MIN_Z;
973 		sbuf[i] = 0;
974 	}
975 };
976 
977 /**
978 	End for rendering of a frame.
979 	@private
980  */
981 JSC3D.Viewer.prototype.endScene = function() {
982 	var data = this.canvasData.data;
983 	var width = this.canvas.width;
984 	var height = this.canvas.height;
985 	var cbuf = this.colorBuffer;
986 	var cwidth = this.frameWidth;
987 	var cheight = this.frameHeight;
988 	var csize = cwidth * cheight;
989 
990 	switch(this.definition) {
991 	case 'low':
992 		var halfWidth = width >> 1;
993 		var surplus = cwidth - halfWidth;
994 		var src = 0, dest = 0;
995 		for(var i=0; i<height; i++) {
996 			for(var j=0; j<width; j++) {
997 				var color = cbuf[src];
998 				data[dest]     = (color & 0xff0000) >> 16;
999 				data[dest + 1] = (color & 0xff00) >> 8;
1000 				data[dest + 2] = color & 0xff;
1001 				data[dest + 3] = 0xff;
1002 				src += (j & 1);
1003 				dest += 4;
1004 			}
1005 			src += (i & 1) ? surplus : -halfWidth;
1006 		}
1007 		break;
1008 	case 'high':
1009 		var src = 0, dest = 0;
1010 		for(var i=0; i<height; i++) {
1011 			for(var j=0; j<width; j++) {
1012 				var color0 = cbuf[src];
1013 				var color1 = cbuf[src + 1];
1014 				var color2 = cbuf[src + cwidth];
1015 				var color3 = cbuf[src + cwidth + 1];
1016 				data[dest]     = ((color0 & 0xff0000) + (color1 & 0xff0000) + (color2 & 0xff0000) + (color3 & 0xff0000)) >> 18;
1017 				data[dest + 1] = ((color0 & 0xff00) + (color1 & 0xff00) + (color2 & 0xff00) + (color3 & 0xff00)) >> 10;
1018 				data[dest + 2] = ((color0 & 0xff) + (color1 & 0xff) + (color2 & 0xff) + (color3 & 0xff)) >> 2;
1019 				data[dest + 3] = 0xff;
1020 				src += 2;
1021 				dest += 4;
1022 			}
1023 			src += cwidth;
1024 		}
1025 		break;
1026 	case 'standard':
1027 	default:
1028 		for(var src=0, dest=0; src<csize; src++, dest+=4) {
1029 			var color = cbuf[src];
1030 			data[dest]     = (color & 0xff0000) >> 16;
1031 			data[dest + 1] = (color & 0xff00) >> 8;
1032 			data[dest + 2] = color & 0xff;
1033 			data[dest + 3] = 0xff;
1034 		}
1035 		break;
1036 	}
1037 };
1038 
1039 /**
1040 	Render a new frame.
1041 	@private
1042  */
1043 JSC3D.Viewer.prototype.render = function() {
1044 	if(this.scene.isEmpty())
1045 		return;
1046 
1047 	var aabb = this.scene.aabb;
1048 
1049 	// calculate transformation matrix
1050 	this.transformMatrix.identity();
1051 	this.transformMatrix.translate(-(aabb.minX+aabb.maxX)/2, -(aabb.minY+aabb.maxY)/2, -(aabb.minZ+aabb.maxZ)/2);
1052 	this.transformMatrix.multiply(this.rotMatrix);
1053 	this.transformMatrix.scale(this.zoomFactor, -this.zoomFactor, this.zoomFactor);
1054 	this.transformMatrix.translate(this.frameWidth/2, this.frameHeight/2, 0);
1055 
1056 	// sort, transform and render the scene
1057 	var renderList = this.sortScene(this.transformMatrix);
1058 	for(var i=0; i<renderList.length; i++) {
1059 		var mesh = renderList[i];
1060 
1061 		if(!mesh.isTrivial()) {
1062 			JSC3D.Math3D.transformVectors(this.transformMatrix, mesh.vertexBuffer, mesh.transformedVertexBuffer);
1063 
1064 			if(mesh.visible) {
1065 				switch(this.renderMode) {
1066 				case 'point':
1067 					this.renderPoint(mesh);
1068 					break;
1069 				case 'wireframe':
1070 					this.renderWireframe(mesh);
1071 					break;
1072 				case 'flat':
1073 					this.renderSolidFlat(mesh);
1074 					break;
1075 				case 'smooth':
1076 					this.renderSolidSmooth(mesh);
1077 					break;
1078 				case 'texture':
1079 					if(mesh.hasTexture())
1080 						this.renderSolidTexture(mesh);
1081 					else
1082 						this.renderSolidFlat(mesh);
1083 					break;
1084 				case 'textureflat':
1085 					if(mesh.hasTexture())
1086 						this.renderTextureFlat(mesh);
1087 					else
1088 						this.renderSolidFlat(mesh);
1089 					break;
1090 				case 'texturesmooth':
1091 					if(mesh.isEnvironmentCast && this.sphereMap != null && this.sphereMap.hasData())
1092 						this.renderSolidSphereMapped(mesh);
1093 					else if(mesh.hasTexture())
1094 						this.renderTextureSmooth(mesh);
1095 					else
1096 						this.renderSolidSmooth(mesh);
1097 					break;
1098 				default:
1099 					this.renderSolidFlat(mesh);
1100 					break;
1101 				}
1102 			}
1103 		}
1104 	}
1105 };
1106 
1107 /**
1108 	Sort meshes inside the scene into a render list. The sorting criterion is a mixture of trnasparency and depth.
1109 	This routine is necessary to ensure a correct rendering order.
1110 	@private
1111  */
1112 JSC3D.Viewer.prototype.sortScene = function(mat) {
1113 	var renderList = [];
1114 
1115 	var meshes = this.scene.getChildren();
1116 	for(var i=0; i<meshes.length; i++) {
1117 		var mesh = meshes[i];
1118 		if(!mesh.isTrivial()) {
1119 			renderList.push(mesh);
1120 			var meshCenter = mesh.aabb.center();
1121 			JSC3D.Math3D.transformVectors(mat, meshCenter, meshCenter);
1122 			var meshMaterial = mesh.material ? mesh.material : this.defaultMaterial;
1123 			mesh.sortKey = { 
1124 				depth: meshCenter[2], 
1125 				isTransparnt: (meshMaterial.transparency > 0) || (mesh.hasTexture() ? mesh.texture.hasTransparency : false)
1126 			};
1127 		}
1128 	}
1129 
1130 	renderList.sort( 
1131 		function(mesh0, mesh1) {
1132 			// opaque meshes should always be prior to transparent ones to be rendered
1133 			if(!mesh0.sortKey.isTransparnt && mesh1.sortKey.isTransparnt)
1134 				return -1;
1135 
1136 			// opaque meshes should always be prior to transparent ones to be rendered
1137 			if(mesh0.sortKey.isTransparnt && !mesh1.sortKey.isTransparnt)
1138 				return 1;
1139 
1140 			// transparent meshes should be rendered from far to near
1141 			if(mesh0.sortKey.isTransparnt)
1142 				return mesh0.sortKey.depth - mesh1.sortKey.depth;
1143 
1144 			// opaque meshes should be rendered form near to far
1145 			return mesh1.sortKey.depth - mesh0.sortKey.depth;
1146 	} );
1147 
1148 	return renderList;
1149 };
1150 
1151 /**
1152 	Render the given mesh as points.
1153 	@private
1154  */
1155 JSC3D.Viewer.prototype.renderPoint = function(mesh) {
1156 	var w = this.frameWidth;
1157 	var h = this.frameHeight;
1158 	var xbound = w - 1;
1159 	var ybound = h - 1;
1160 	var ibuf = mesh.indexBuffer;
1161 	var vbuf = mesh.transformedVertexBuffer;
1162 	var nbuf = mesh.transformedVertexNormalZBuffer;
1163 	var cbuf = this.colorBuffer;
1164 	var zbuf = this.zBuffer;
1165 	var sbuf = this.selectionBuffer;
1166 	var numOfVertices = vbuf.length / 3;
1167 	var id = mesh.internalId;
1168 	var color = mesh.material ? mesh.material.diffuseColor : this.defaultMaterial.diffuseColor;
1169 	
1170 	if(!nbuf || nbuf.length < numOfVertices) {
1171 		mesh.transformedVertexNormalZBuffer = new Array(numOfVertices);
1172 		nbuf = mesh.transformedVertexNormalZBuffer;
1173 	}
1174 
1175 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.vertexNormalBuffer, nbuf);
1176 
1177 	for(var i=0, j=0; i<numOfVertices; i++, j+=3) {
1178 		var xformedNz = nbuf[i];
1179 		if(mesh.isDoubleSided)
1180 			xformedNz = xformedNz > 0 ? xformedNz : -xformedNz;
1181 		if(xformedNz > 0) {
1182 			var x = ~~(vbuf[j]     + 0.5);
1183 			var y = ~~(vbuf[j + 1] + 0.5);
1184 			var z = vbuf[j + 2];
1185 			if(x >=0 && x < xbound && y >=0 && y < ybound) {
1186 				var pix = y * w + x;
1187 				if(z > zbuf[pix]) {
1188 					zbuf[pix] = z;
1189 					cbuf[pix] = color;
1190 					sbuf[pix] = id;
1191 				}
1192 				pix++;
1193 				if(z > zbuf[pix]) {
1194 					zbuf[pix] = z;
1195 					cbuf[pix] = color;
1196 					sbuf[pix] = id;
1197 				}
1198 				pix += xbound;
1199 				if(z > zbuf[pix]) {
1200 					zbuf[pix] = z;
1201 					cbuf[pix] = color;
1202 					sbuf[pix] = id;
1203 				}
1204 				pix++;
1205 				if(z > zbuf[pix]) {
1206 					zbuf[pix] = z;
1207 					cbuf[pix] = color;
1208 					sbuf[pix] = id;
1209 				}
1210 			}
1211 		}
1212 	}
1213 };
1214 
1215 /**
1216 	Render the given mesh as wireframe.
1217 	@private
1218  */
1219 JSC3D.Viewer.prototype.renderWireframe = function(mesh) {
1220 	var w = this.frameWidth;
1221 	var h = this.frameHeight;
1222 	var xbound = w - 1;
1223 	var ybound = h - 1;
1224 	var ibuf = mesh.indexBuffer;
1225 	var vbuf = mesh.transformedVertexBuffer;
1226 	var nbuf = mesh.transformedFaceNormalZBuffer;
1227 	var cbuf = this.colorBuffer;
1228 	var zbuf = this.zBuffer;
1229 	var sbuf = this.selectionBuffer;
1230 	var numOfFaces = mesh.faceCount;
1231 	var id = mesh.internalId;
1232 	var color = mesh.material ? mesh.material.diffuseColor : this.defaultMaterial.diffuseColor;
1233 
1234 	if(!nbuf || nbuf.length < numOfFaces) {
1235 		mesh.transformedFaceNormalZBuffer = new Array(numOfFaces);
1236 		nbuf = mesh.transformedFaceNormalZBuffer;
1237 	}
1238 
1239 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.faceNormalBuffer, nbuf);
1240 
1241 	var i = 0, j = 0;
1242 	while(i < numOfFaces) {
1243 		var xformedNz = nbuf[i++];
1244 		if(mesh.isDoubleSided)
1245 			xformedNz = xformedNz > 0 ? xformedNz : -xformedNz;
1246 		if(xformedNz < 0) {
1247 			do {
1248 			} while (ibuf[j++] != -1);
1249 		}
1250 		else {
1251 			var vStart, v0, v1;
1252 			v0 = ibuf[j++] * 3;
1253 			v1 = ibuf[j++] * 3;
1254 			vStart = v0;
1255 
1256 			var isClosed = false;
1257 			while(!isClosed) {
1258 				var x0 = ~~(vbuf[v0]     + 0.5);
1259 				var y0 = ~~(vbuf[v0 + 1] + 0.5);
1260 				var z0 = vbuf[v0 + 2];
1261 				var x1 = ~~(vbuf[v1]     + 0.5);
1262 				var y1 = ~~(vbuf[v1 + 1] + 0.5);
1263 				var z1 = vbuf[v1 + 2];
1264 
1265 				var dx = x1 - x0;
1266 				var dy = y1 - y0;
1267 				var dz = z1 - z0;
1268 
1269 				var dd;
1270 				var xInc, yInc, zInc;
1271 				if(Math.abs(dx) > Math.abs(dy)) {
1272 					dd = dx;
1273 					xInc = dx > 0 ? 1 : -1;
1274 					yInc = dx != 0 ? xInc * dy / dx : 0;
1275 					zInc = dx != 0 ? xInc * dz / dx : 0;
1276 				}
1277 				else {
1278 					dd = dy;
1279 					yInc = dy > 0 ? 1 : -1;
1280 					xInc = dy != 0 ? yInc * dx / dy : 0;
1281 					zInc = dy != 0 ? yInc * dz / dy : 0;
1282 				}
1283 
1284 				var x = x0;
1285 				var y = y0;
1286 				var z = z0;
1287 
1288 				if(dd < 0) {
1289 					x = x1;
1290 					y = y1;
1291 					z = z1;
1292 					dd = -dd;
1293 					xInc = -xInc;
1294 					yInc = -yInc;
1295 					zInc = -zInc;
1296 				}
1297 
1298 				for(var k=0; k<dd; k++) {
1299 					if(x >=0 && x < xbound && y >=0 && y < ybound) {
1300 						var pix = (~~y) * w + (~~x);
1301 						if(z > zbuf[pix]) {
1302 							zbuf[pix] = z;
1303 							cbuf[pix] = color;
1304 							sbuf[pix] = id;
1305 						}
1306 					}
1307 
1308 					x += xInc;
1309 					y += yInc;
1310 					z += zInc;
1311 				}
1312 
1313 				if(v1 == vStart) {
1314 					isClosed = true;
1315 				}
1316 				else {
1317 					v0 = v1;
1318 
1319 					if(ibuf[j] != -1) {
1320 						v1 = ibuf[j++] * 3;
1321 					}
1322 					else {
1323 						v1 = vStart;
1324 					}
1325 				}
1326 			}
1327 
1328 			j++;
1329 		}
1330 	}
1331 };
1332 
1333 /**
1334 	Render the given mesh as solid object, using flat shading.
1335 	@private
1336  */
1337 JSC3D.Viewer.prototype.renderSolidFlat = function(mesh) {
1338 	var w = this.frameWidth;
1339 	var h = this.frameHeight;
1340 	var ibuf = mesh.indexBuffer;
1341 	var vbuf = mesh.transformedVertexBuffer;
1342 	var nbuf = mesh.transformedFaceNormalZBuffer;
1343 	var cbuf = this.colorBuffer;
1344 	var zbuf = this.zBuffer;
1345 	var sbuf = this.selectionBuffer;
1346 	var numOfFaces = mesh.faceCount;
1347 	var id = mesh.internalId;
1348 	var material = mesh.material ? mesh.material : this.defaultMaterial;
1349 	var palette = material.getPalette();
1350 	var isOpaque = material.transparency == 0;
1351 	var trans = material.transparency * 255;
1352 	var opaci = 255 - trans;
1353 
1354 	// skip this mesh if it is completely transparent
1355 	if(material.transparency == 1)
1356 		return;
1357 
1358 	if(!nbuf || nbuf.length < numOfFaces) {
1359 		mesh.transformedFaceNormalZBuffer = new Array(numOfFaces);
1360 		nbuf = mesh.transformedFaceNormalZBuffer;
1361 	}
1362 
1363 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.faceNormalBuffer, nbuf);
1364 
1365 	var Xs = new Array(3);
1366 	var Ys = new Array(3);
1367 	var Zs = new Array(3);
1368 	var i = 0, j = 0;
1369 	while(i < numOfFaces) {
1370 		var xformedNz = nbuf[i++];
1371 		if(mesh.isDoubleSided)
1372 			xformedNz = xformedNz > 0 ? xformedNz : -xformedNz;
1373 		if(xformedNz < 0) {
1374 			do {
1375 			} while (ibuf[j++] != -1);
1376 		}
1377 		else {
1378 			var color = palette[~~(xformedNz * 255)];
1379 
1380 			var v0, v1, v2;
1381 			v0 = ibuf[j++] * 3;
1382 			v1 = ibuf[j++] * 3;
1383 
1384 			do {
1385 				v2 = ibuf[j++] * 3;
1386 
1387 				Xs[0] = ~~(vbuf[v0]     + 0.5);
1388 				Ys[0] = ~~(vbuf[v0 + 1] + 0.5);
1389 				Zs[0] = vbuf[v0 + 2];
1390 				Xs[1] = ~~(vbuf[v1]     + 0.5);
1391 				Ys[1] = ~~(vbuf[v1 + 1] + 0.5);
1392 				Zs[1] = vbuf[v1 + 2];
1393 				Xs[2] = ~~(vbuf[v2]     + 0.5);
1394 				Ys[2] = ~~(vbuf[v2 + 1] + 0.5);
1395 				Zs[2] = vbuf[v2 + 2];
1396 
1397 				var high = Ys[0] < Ys[1] ? 0 : 1;
1398 				high = Ys[high] < Ys[2] ? high : 2;
1399 				var low = Ys[0] > Ys[1] ? 0 : 1;
1400 				low = Ys[low] > Ys[2] ? low : 2;
1401 				var mid = 3 - low - high;
1402 
1403 				if(high != low) {
1404 					var x0 = Xs[low];
1405 					var z0 = Zs[low];
1406 					var dy0 = Ys[low] - Ys[high];
1407 					dy0 = dy0 != 0 ? dy0 : 1;
1408 					var xStep0 = (Xs[low] - Xs[high]) / dy0;
1409 					var zStep0 = (Zs[low] - Zs[high]) / dy0;
1410 
1411 					var x1 = Xs[low];
1412 					var z1 = Zs[low];
1413 					var dy1 = Ys[low] - Ys[mid];
1414 					dy1 = dy1 != 0 ? dy1 : 1;
1415 					var xStep1 = (Xs[low] - Xs[mid]) / dy1;
1416 					var zStep1 = (Zs[low] - Zs[mid]) / dy1;
1417 
1418 					var x2 = Xs[mid];
1419 					var z2 = Zs[mid];
1420 					var dy2 = Ys[mid] - Ys[high];
1421 					dy2 = dy2 != 0 ? dy2 : 1;
1422 					var xStep2 = (Xs[mid] - Xs[high]) / dy2;
1423 					var zStep2 = (Zs[mid] - Zs[high]) / dy2;
1424 
1425 					var linebase = Ys[low] * w;
1426 					for(var y=Ys[low]; y>Ys[high]; y--) {
1427 						if(y >=0 && y < h) {
1428 							var xLeft = ~~x0;
1429 							var zLeft = z0;
1430 							var xRight, zRight;
1431 							if(y > Ys[mid]) {
1432 								xRight = ~~x1;
1433 								zRight = z1;
1434 							}
1435 							else {
1436 								xRight = ~~x2;
1437 								zRight = z2;
1438 							}
1439 
1440 							if(xLeft > xRight) {
1441 								var temp;
1442 								temp = xLeft;
1443 								xLeft = xRight;
1444 								xRight = temp;
1445 								temp = zLeft;
1446 								zLeft = zRight;
1447 								zRight = temp;
1448 							}
1449 
1450 							if(xLeft < 0)
1451 								xLeft = 0;
1452 							if(xRight >= w)
1453 								xRight = w - 1;
1454 
1455 							var zInc = (xLeft != xRight) ? ((zRight - zLeft) / (xRight - xLeft)) : 1;
1456 							var pix = linebase + xLeft;
1457 							if(isOpaque) {
1458 								for(var x=xLeft, z=zLeft; x<=xRight; x++, z+=zInc) {
1459 									if(z > zbuf[pix]) {
1460 										zbuf[pix] = z;
1461 										cbuf[pix] = color;
1462 										sbuf[pix] = id;
1463 									}
1464 									pix++;
1465 								}
1466 							}
1467 							else {
1468 								for(var x=xLeft, z=zLeft; x<xRight; x++, z+=zInc) {
1469 									if(z > zbuf[pix]) {
1470 										var foreColor = color;
1471 										var backColor = cbuf[pix];
1472 										var rr = ((backColor & 0xff0000) * trans + (foreColor & 0xff0000) * opaci) >> 8;
1473 										var gg = ((backColor & 0xff00) * trans + (foreColor & 0xff00) * opaci) >> 8;
1474 										var bb = ((backColor & 0xff) * trans + (foreColor & 0xff) * opaci) >> 8;
1475 										cbuf[pix] = (rr & 0xff0000) | (gg & 0xff00) | (bb & 0xff);
1476 										sbuf[pix] = id;
1477 									}
1478 									pix++;
1479 								}
1480 							}
1481 						}
1482 
1483 						// step up to next scanline
1484 						//
1485 						x0 -= xStep0;
1486 						z0 -= zStep0;
1487 						if(y > Ys[mid]) {
1488 							x1 -= xStep1;
1489 							z1 -= zStep1;
1490 						}
1491 						else {
1492 							x2 -= xStep2;
1493 							z2 -= zStep2;
1494 						}
1495 						linebase -= w;
1496 					}
1497 				}
1498 
1499 				v1 = v2;
1500 			} while (ibuf[j] != -1);
1501 
1502 			j++;
1503 		}
1504 	}
1505 };
1506 
1507 /**
1508 	Render the given mesh as solid object, using smooth shading.
1509 	@private
1510  */
1511 JSC3D.Viewer.prototype.renderSolidSmooth = function(mesh) {
1512 	var w = this.frameWidth;
1513 	var h = this.frameHeight;
1514 	var ibuf = mesh.indexBuffer;
1515 	var vbuf = mesh.transformedVertexBuffer;
1516 	var vnbuf = mesh.transformedVertexNormalZBuffer;
1517 	var fnbuf = mesh.transformedFaceNormalZBuffer;
1518 	var cbuf = this.colorBuffer;
1519 	var zbuf = this.zBuffer;
1520 	var sbuf = this.selectionBuffer;
1521 	var numOfFaces = mesh.faceCount;
1522 	var numOfVertices = vbuf.length / 3;
1523 	var id = mesh.internalId;
1524 	var material = mesh.material ? mesh.material : this.defaultMaterial;
1525 	var palette = material.getPalette();
1526 	var isOpaque = material.transparency == 0;
1527 	var trans = material.transparency * 255;
1528 	var opaci = 255 - trans;
1529 
1530 	// skip this mesh if it is completely transparent
1531 	if(material.transparency == 1)
1532 		return;
1533 
1534 	if(!vnbuf || vnbuf.length < numOfVertices) {
1535 		mesh.transformedVertexNormalZBuffer = new Array(numOfVertices);
1536 		vnbuf = mesh.transformedVertexNormalZBuffer;
1537 	}
1538 
1539 	if(!fnbuf || fnbuf.length < numOfFaces) {
1540 		mesh.transformedFaceNormalZBuffer = new Array(numOfFaces);
1541 		fnbuf = mesh.transformedFaceNormalZBuffer;
1542 	}
1543 
1544 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.vertexNormalBuffer, vnbuf);
1545 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.faceNormalBuffer, fnbuf);
1546 
1547 	var isDoubleSided = mesh.isDoubleSided;
1548 
1549 	var Xs = new Array(3);
1550 	var Ys = new Array(3);
1551 	var Zs = new Array(3);
1552 	var Ns = new Array(3);
1553 	var i = 0, j = 0;
1554 	while(i < numOfFaces) {
1555 		var xformedFNz = fnbuf[i++];
1556 		if(isDoubleSided)
1557 			xformedFNz = xformedFNz > 0 ? xformedFNz : -xformedFNz;
1558 		if(xformedFNz < 0) {
1559 			do {
1560 			} while (ibuf[j++] != -1);
1561 		}
1562 		else {
1563 			var i0, i1, i2;
1564 			var v0, v1, v2;
1565 			i0 = ibuf[j++];
1566 			v0 = i0 * 3;
1567 			i1 = ibuf[j++];
1568 			v1 = i1 * 3;
1569 
1570 			do {
1571 				i2 = ibuf[j++];
1572 				v2 = i2 * 3;
1573 
1574 				Xs[0] = ~~(vbuf[v0]     + 0.5);
1575 				Ys[0] = ~~(vbuf[v0 + 1] + 0.5);
1576 				Zs[0] = vbuf[v0 + 2];
1577 				Xs[1] = ~~(vbuf[v1]     + 0.5);
1578 				Ys[1] = ~~(vbuf[v1 + 1] + 0.5);
1579 				Zs[1] = vbuf[v1 + 2];
1580 				Xs[2] = ~~(vbuf[v2]     + 0.5);
1581 				Ys[2] = ~~(vbuf[v2 + 1] + 0.5);
1582 				Zs[2] = vbuf[v2 + 2];
1583 
1584 				Ns[0] = vnbuf[i0];
1585 				Ns[1] = vnbuf[i1];
1586 				Ns[2] = vnbuf[i2];
1587 				if(isDoubleSided) {
1588 					if(Ns[0] < 0)
1589 						Ns[0] = -Ns[0];
1590 					if(Ns[1] < 0)
1591 						Ns[1] = -Ns[1];
1592 					if(Ns[2] < 0)
1593 						Ns[2] = -Ns[2];
1594 				}
1595 
1596 				var high = Ys[0] < Ys[1] ? 0 : 1;
1597 				high = Ys[high] < Ys[2] ? high : 2;
1598 				var low = Ys[0] > Ys[1] ? 0 : 1;
1599 				low = Ys[low] > Ys[2] ? low : 2;
1600 				var mid = 3 - low - high;
1601 
1602 				if(high != low) {
1603 					var x0 = Xs[low];
1604 					var z0 = Zs[low];
1605 					var n0 = Ns[low] * 255;
1606 					var dy0 = Ys[low] - Ys[high];
1607 					dy0 = dy0 != 0 ? dy0 : 1;
1608 					var xStep0 = (Xs[low] - Xs[high]) / dy0;
1609 					var zStep0 = (Zs[low] - Zs[high]) / dy0;
1610 					var nStep0 = (Ns[low] - Ns[high]) * 255 / dy0;
1611 
1612 					var x1 = Xs[low];
1613 					var z1 = Zs[low];
1614 					var n1 = Ns[low] * 255;
1615 					var dy1 = Ys[low] - Ys[mid];
1616 					dy1 = dy1 != 0 ? dy1 : 1;
1617 					var xStep1 = (Xs[low] - Xs[mid]) / dy1;
1618 					var zStep1 = (Zs[low] - Zs[mid]) / dy1;
1619 					var nStep1 = (Ns[low] - Ns[mid]) * 255 / dy1;
1620 
1621 					var x2 = Xs[mid];
1622 					var z2 = Zs[mid];
1623 					var n2 = Ns[mid] * 255;
1624 					var dy2 = Ys[mid] - Ys[high];
1625 					dy2 = dy2 != 0 ? dy2 : 1;
1626 					var xStep2 = (Xs[mid] - Xs[high]) / dy2;
1627 					var zStep2 = (Zs[mid] - Zs[high]) / dy2;
1628 					var nStep2 = (Ns[mid] - Ns[high]) * 255 / dy2;
1629 
1630 					var linebase = Ys[low] * w;
1631 					for(var y=Ys[low]; y>Ys[high]; y--) {
1632 						if(y >=0 && y < h) {
1633 							var xLeft = ~~x0;
1634 							var zLeft = z0;
1635 							var nLeft = n0;
1636 							var xRight, zRight, nRight;
1637 							if(y > Ys[mid]) {
1638 								xRight = ~~x1;
1639 								zRight = z1;
1640 								nRight = n1;
1641 							}
1642 							else {
1643 								xRight = ~~x2;
1644 								zRight = z2;
1645 								nRight = n2;
1646 							}
1647 
1648 							if(xLeft > xRight) {
1649 								var temp;
1650 								temp = xLeft;
1651 								xLeft = xRight;
1652 								xRight = temp;
1653 								temp = zLeft;
1654 								zLeft = zRight;
1655 								zRight = temp;
1656 								temp = nLeft;
1657 								nLeft = nRight;
1658 								nRight = temp;
1659 							}
1660 
1661 							var zInc = (xLeft != xRight) ? ((zRight - zLeft) / (xRight - xLeft)) : 1;
1662 							var nInc = (xLeft != xRight) ? ((nRight - nLeft) / (xRight - xLeft)) : 1;
1663 							if(xLeft < 0) {
1664 								zLeft -= xLeft * zInc;
1665 								nLeft -= xLeft * nInc;
1666 								xLeft = 0;
1667 							}
1668 							if(xRight >= w) {
1669 								xRight = w - 1;
1670 							}
1671 							var pix = linebase + xLeft;
1672 							if(isOpaque) {
1673 								for(var x=xLeft, z=zLeft, n=nLeft; x<=xRight; x++, z+=zInc, n+=nInc) {
1674 									if(z > zbuf[pix]) {
1675 										zbuf[pix] = z;
1676 										cbuf[pix] = palette[n > 0 ? (~~n) : 0];
1677 										sbuf[pix] = id;
1678 									}
1679 									pix++;
1680 								}
1681 							}
1682 							else {
1683 								for(var x=xLeft, z=zLeft, n=nLeft; x<xRight; x++, z+=zInc, n+=nInc) {
1684 									if(z > zbuf[pix]) {
1685 										var foreColor = palette[n > 0 ? (~~n) : 0];
1686 										var backColor = cbuf[pix];
1687 										var rr = ((backColor & 0xff0000) * trans + (foreColor & 0xff0000) * opaci) >> 8;
1688 										var gg = ((backColor & 0xff00) * trans + (foreColor & 0xff00) * opaci) >> 8;
1689 										var bb = ((backColor & 0xff) * trans + (foreColor & 0xff) * opaci) >> 8;
1690 										cbuf[pix] = (rr & 0xff0000) | (gg & 0xff00) | (bb & 0xff);
1691 										sbuf[pix] = id;
1692 									}
1693 									pix++;
1694 								}
1695 							}
1696 						}
1697 
1698 						// step up to next scanline
1699 						//
1700 						x0 -= xStep0;
1701 						z0 -= zStep0;
1702 						n0 -= nStep0;
1703 						if(y > Ys[mid]) {
1704 							x1 -= xStep1;
1705 							z1 -= zStep1;
1706 							n1 -= nStep1;
1707 						}
1708 						else {
1709 							x2 -= xStep2;
1710 							z2 -= zStep2;
1711 							n2 -= nStep2;
1712 						}
1713 						linebase -= w;
1714 					}
1715 				}
1716 
1717 				v1 = v2;
1718 				i1 = i2;
1719 			} while (ibuf[j] != -1);
1720 
1721 			j++;
1722 		}
1723 	}
1724 };
1725 
1726 /**
1727 	Render the given mesh as textured object, with no lightings.
1728 	@private
1729  */
1730 JSC3D.Viewer.prototype.renderSolidTexture = function(mesh) {
1731 	var w = this.frameWidth;
1732 	var h = this.frameHeight;
1733 	var ibuf = mesh.indexBuffer;
1734 	var vbuf = mesh.transformedVertexBuffer;
1735 	var nbuf = mesh.transformedFaceNormalZBuffer;
1736 	var cbuf = this.colorBuffer;
1737 	var zbuf = this.zBuffer;
1738 	var sbuf = this.selectionBuffer;
1739 	var numOfFaces = mesh.faceCount;
1740 	var id = mesh.internalId;
1741 	var texture = mesh.texture;
1742 	var isOpaque = !texture.hasTransparency;
1743 	var tbuf = mesh.texCoordBuffer;
1744 	var tibuf = mesh.texCoordIndexBuffer;
1745 	var tdata = texture.data;
1746 	var tdim = texture.width;
1747 	var tbound = tdim - 1;
1748 	var mipmaps = texture.hasMipmap() ? texture.mipmaps : null;
1749 	var mipentries = mipmaps ? texture.mipentries : null;
1750 
1751 	if(!nbuf || nbuf.length < numOfFaces) {
1752 		mesh.transformedFaceNormalZBuffer = new Array(numOfFaces);
1753 		nbuf = mesh.transformedFaceNormalZBuffer;
1754 	}
1755 
1756 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.faceNormalBuffer, nbuf);
1757 
1758 	var Xs = new Array(3);
1759 	var Ys = new Array(3);
1760 	var Zs = new Array(3);
1761 	var THs = new Array(3);
1762 	var TVs = new Array(3);
1763 	var i = 0, j = 0;
1764 	while(i < numOfFaces) {
1765 		var xformedNz = nbuf[i++];
1766 		if(mesh.isDoubleSided)
1767 			xformedNz = xformedNz > 0 ? xformedNz : -xformedNz;
1768 		if(xformedNz < 0) {
1769 			do {
1770 			} while (ibuf[j++] != -1);
1771 		}
1772 		else {
1773 			var v0, v1, v2;
1774 			var t0, t1, t2;
1775 			v0 = ibuf[j] * 3;
1776 			t0 = tibuf[j] * 2;
1777 			j++;
1778 			v1 = ibuf[j] * 3;
1779 			t1 = tibuf[j] * 2;
1780 			j++;
1781 
1782 			// select an appropriate mip-map level for texturing
1783 			//
1784 			if(mipmaps) {
1785 				v2 = ibuf[j] * 3;
1786 				t2 = tibuf[j] * 2;
1787 
1788 				tdim = texture.width;
1789 
1790 				Xs[0] = vbuf[v0];
1791 				Ys[0] = vbuf[v0 + 1];
1792 				Xs[1] = vbuf[v1];
1793 				Ys[1] = vbuf[v1 + 1];
1794 				Xs[2] = vbuf[v2];
1795 				Ys[2] = vbuf[v2 + 1];
1796 
1797 				THs[0] = tbuf[t0] * tdim;
1798 				TVs[0] = tbuf[t0 + 1] * tdim;
1799 				THs[1] = tbuf[t1] * tdim;
1800 				TVs[1] = tbuf[t1 + 1] * tdim;
1801 				THs[2] = tbuf[t2] * tdim;
1802 				TVs[2] = tbuf[t2 + 1] * tdim;
1803 
1804 				var faceArea = (Xs[1] - Xs[0]) * (Ys[2] - Ys[0]) - (Ys[1] - Ys[0]) * (Xs[2] - Xs[0]);
1805 				if(faceArea < 0)
1806 					faceArea = -faceArea;
1807 				faceArea += 1;
1808 				var texArea = (THs[1] - THs[0]) * (TVs[2] - TVs[0]) - (TVs[1] -  TVs[0]) * (THs[2] - THs[0]);
1809 				if(texArea < 0)
1810 					texArea = -texArea;
1811 				var mipRatio = texArea / faceArea;
1812 
1813 				var level = 0;
1814 				if(mipRatio < mipentries[1])
1815 					level = 0;
1816 				else if(mipRatio >= mipentries[mipentries.length - 1]) {
1817 					level = mipentries.length - 1;
1818 					tdim = 1;
1819 				}
1820 				else {
1821 					while(mipRatio >= mipentries[level+1]) {
1822 						level++;
1823 						tdim /= 2;
1824 					}
1825 				}
1826 
1827 				tdata = mipmaps[level];
1828 				tbound = tdim - 1;
1829 			}
1830 
1831 			do {
1832 				v2 = ibuf[j] * 3;
1833 				t2 = tibuf[j] * 2;
1834 				j++;
1835 
1836 				Xs[0] = ~~(vbuf[v0]     + 0.5);
1837 				Ys[0] = ~~(vbuf[v0 + 1] + 0.5);
1838 				Zs[0] = vbuf[v0 + 2];
1839 				Xs[1] = ~~(vbuf[v1]     + 0.5);
1840 				Ys[1] = ~~(vbuf[v1 + 1] + 0.5);
1841 				Zs[1] = vbuf[v1 + 2];
1842 				Xs[2] = ~~(vbuf[v2]     + 0.5);
1843 				Ys[2] = ~~(vbuf[v2 + 1] + 0.5);
1844 				Zs[2] = vbuf[v2 + 2];
1845 
1846 				THs[0] = tbuf[t0] * tdim;
1847 				TVs[0] = tbuf[t0 + 1] * tdim;
1848 				THs[1] = tbuf[t1] * tdim;
1849 				TVs[1] = tbuf[t1 + 1] * tdim;
1850 				THs[2] = tbuf[t2] * tdim;
1851 				TVs[2] = tbuf[t2 + 1] * tdim;
1852 
1853 				var high = Ys[0] < Ys[1] ? 0 : 1;
1854 				high = Ys[high] < Ys[2] ? high : 2;
1855 				var low = Ys[0] > Ys[1] ? 0 : 1;
1856 				low = Ys[low] > Ys[2] ? low : 2;
1857 				var mid = 3 - low - high;
1858 
1859 				if(high != low) {
1860 					var x0 = Xs[low];
1861 					var z0 = Zs[low];
1862 					var th0 = THs[low];
1863 					var tv0 = TVs[low];
1864 					var dy0 = Ys[low] - Ys[high];
1865 					dy0 = dy0 != 0 ? dy0 : 1;
1866 					var xStep0 = (Xs[low] - Xs[high]) / dy0;
1867 					var zStep0 = (Zs[low] - Zs[high]) / dy0;
1868 					var thStep0 = (THs[low] - THs[high]) / dy0;
1869 					var tvStep0 = (TVs[low] - TVs[high]) / dy0;
1870 
1871 					var x1 = Xs[low];
1872 					var z1 = Zs[low];
1873 					var th1 = THs[low];
1874 					var tv1 = TVs[low];
1875 					var dy1 = Ys[low] - Ys[mid];
1876 					dy1 = dy1 != 0 ? dy1 : 1;
1877 					var xStep1 = (Xs[low] - Xs[mid]) / dy1;
1878 					var zStep1 = (Zs[low] - Zs[mid]) / dy1;
1879 					var thStep1 = (THs[low] - THs[mid]) / dy1;
1880 					var tvStep1 = (TVs[low] - TVs[mid]) / dy1;
1881 
1882 					var x2 = Xs[mid];
1883 					var z2 = Zs[mid];
1884 					var th2 = THs[mid];
1885 					var tv2 = TVs[mid];
1886 					var dy2 = Ys[mid] - Ys[high];
1887 					dy2 = dy2 != 0 ? dy2 : 1;
1888 					var xStep2 = (Xs[mid] - Xs[high]) / dy2;
1889 					var zStep2 = (Zs[mid] - Zs[high]) / dy2;
1890 					var thStep2 = (THs[mid] - THs[high]) / dy2;
1891 					var tvStep2 = (TVs[mid] - TVs[high]) / dy2;
1892 
1893 					var linebase = Ys[low] * w;
1894 					for(var y=Ys[low]; y>Ys[high]; y--) {
1895 						if(y >=0 && y < h) {
1896 							var xLeft = ~~x0;
1897 							var zLeft = z0;
1898 							var thLeft = th0;
1899 							var tvLeft = tv0;
1900 							var xRight, zRight, thRight, tvRight;
1901 							if(y > Ys[mid]) {
1902 								xRight = ~~x1;
1903 								zRight = z1;
1904 								thRight = th1;
1905 								tvRight = tv1;
1906 							}
1907 							else {
1908 								xRight = ~~x2;
1909 								zRight = z2;
1910 								thRight = th2;
1911 								tvRight = tv2;
1912 							}
1913 
1914 							if(xLeft > xRight) {
1915 								var temp;
1916 								temp = xLeft;
1917 								xLeft = xRight;
1918 								xRight = temp;
1919 								temp = zLeft;
1920 								zLeft = zRight;
1921 								zRight = temp;
1922 								temp = thLeft;
1923 								thLeft = thRight;
1924 								thRight = temp;
1925 								temp = tvLeft;
1926 								tvLeft = tvRight;
1927 								tvRight = temp;
1928 							}
1929 
1930 							var zInc = (xLeft != xRight) ? ((zRight - zLeft) / (xRight - xLeft)) : 1;
1931 							var thInc = (xLeft != xRight) ? ((thRight - thLeft) / (xRight - xLeft)) : 1;
1932 							var tvInc = (xLeft != xRight) ? ((tvRight - tvLeft) / (xRight - xLeft)) : 1;
1933 
1934 							if(xLeft < 0) {
1935 								zLeft -= xLeft * zInc;
1936 								thLeft -= xLeft * thInc;
1937 								tvLeft -= xLeft * tvInc;
1938 								xLeft = 0;
1939 							}
1940 							if(xRight >= w)
1941 								xRight = w - 1;
1942 
1943 							var pix = linebase + xLeft;
1944 							if(isOpaque) {
1945 								for(var x=xLeft, z=zLeft, th=thLeft, tv=tvLeft; x<=xRight; x++, z+=zInc, th+=thInc, tv+=tvInc) {
1946 									if(z > zbuf[pix]) {
1947 										zbuf[pix] = z;
1948 										cbuf[pix] = tdata[(tv & tbound) * tdim + (th & tbound)];
1949 										sbuf[pix] = id;
1950 									}
1951 									pix++;
1952 								}
1953 							}
1954 							else {
1955 								for(var x=xLeft, z=zLeft, th=thLeft, tv=tvLeft; x<xRight; x++, z+=zInc, th+=thInc, tv+=tvInc) {
1956 									if(z > zbuf[pix]) {
1957 										var foreColor = tdata[(tv & tbound) * tdim + (th & tbound)];
1958 										var backColor = cbuf[pix];
1959 										var opaci = (foreColor >> 24) & 0xff;
1960 										var trans = 255 - opaci;
1961 										var rr = ((backColor & 0xff0000) * trans + (foreColor & 0xff0000) * opaci) >> 8;
1962 										var gg = ((backColor & 0xff00) * trans + (foreColor & 0xff00) * opaci) >> 8;
1963 										var bb = ((backColor & 0xff) * trans + (foreColor & 0xff) * opaci) >> 8;
1964 										cbuf[pix] = (rr & 0xff0000) | (gg & 0xff00) | (bb & 0xff);
1965 										sbuf[pix] = id;
1966 									}
1967 									pix++;
1968 								}
1969 							}
1970 						}
1971 
1972 						// step up to next scanline
1973 						//
1974 						x0 -= xStep0;
1975 						z0 -= zStep0;
1976 						th0 -= thStep0;
1977 						tv0 -= tvStep0;
1978 						if(y > Ys[mid]) {
1979 							x1 -= xStep1;
1980 							z1 -= zStep1;
1981 							th1 -= thStep1;
1982 							tv1 -= tvStep1;
1983 						}
1984 						else {
1985 							x2 -= xStep2;
1986 							z2 -= zStep2;
1987 							th2 -= thStep2;
1988 							tv2 -= tvStep2;
1989 						}
1990 						linebase -= w;
1991 					}
1992 				}
1993 
1994 				v1 = v2;
1995 				t1 = t2;
1996 			} while (ibuf[j] != -1);
1997 
1998 			j++;
1999 		}
2000 	}
2001 };
2002 
2003 /**
2004 	Render the given mesh as textured object. Lighting will be calculated per face.
2005 	@private
2006  */
2007 JSC3D.Viewer.prototype.renderTextureFlat = function(mesh) {
2008 	var w = this.frameWidth;
2009 	var h = this.frameHeight;
2010 	var ibuf = mesh.indexBuffer;
2011 	var vbuf = mesh.transformedVertexBuffer;
2012 	var nbuf = mesh.transformedFaceNormalZBuffer;
2013 	var cbuf = this.colorBuffer;
2014 	var zbuf = this.zBuffer;
2015 	var sbuf = this.selectionBuffer;
2016 	var numOfFaces = mesh.faceCount;
2017 	var id = mesh.internalId;
2018 	var material = mesh.material ? mesh.material : this.defaultMaterial;
2019 	var palette = material.getPalette();
2020 	var texture = mesh.texture;
2021 	var isOpaque = (material.transparency == 0) && !texture.hasTransparency;
2022 	var matOpacity = ~~((1 - material.transparency) * 255);
2023 	var tbuf = mesh.texCoordBuffer;
2024 	var tibuf = mesh.texCoordIndexBuffer;
2025 	var tdata = texture.data;
2026 	var tdim = texture.width;
2027 	var tbound = tdim - 1;
2028 	var mipmaps = texture.hasMipmap() ? texture.mipmaps : null;
2029 	var mipentries = mipmaps ? texture.mipentries : null;
2030 
2031 	// skip this mesh if it is completely transparent
2032 	if(material.transparency == 1)
2033 		return;
2034 
2035 	if(!nbuf || nbuf.length < numOfFaces) {
2036 		mesh.transformedFaceNormalZBuffer = new Array(numOfFaces);
2037 		nbuf = mesh.transformedFaceNormalZBuffer;
2038 	}
2039 
2040 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.faceNormalBuffer, nbuf);
2041 
2042 	var Xs = new Array(3);
2043 	var Ys = new Array(3);
2044 	var Zs = new Array(3);
2045 	var THs = new Array(3);
2046 	var TVs = new Array(3);
2047 	var i = 0, j = 0;
2048 	while(i < numOfFaces) {
2049 		var xformedNz = nbuf[i++];
2050 		if(mesh.isDoubleSided)
2051 			xformedNz = xformedNz > 0 ? xformedNz : -xformedNz;
2052 		if(xformedNz < 0) {
2053 			do {
2054 			} while (ibuf[j++] != -1);
2055 		}
2056 		else {
2057 			var color = palette[~~(xformedNz * 255)];
2058 
2059 			var v0, v1, v2;
2060 			var t0, t1, t2;
2061 			v0 = ibuf[j] * 3;
2062 			t0 = tibuf[j] * 2;
2063 			j++;
2064 			v1 = ibuf[j] * 3;
2065 			t1 = tibuf[j] * 2;
2066 			j++;
2067 
2068 			if(mipmaps) {
2069 				v2 = ibuf[j] * 3;
2070 				t2 = tibuf[j] * 2;
2071 
2072 				tdim = texture.width;
2073 
2074 				Xs[0] = vbuf[v0];
2075 				Ys[0] = vbuf[v0 + 1];
2076 				Xs[1] = vbuf[v1];
2077 				Ys[1] = vbuf[v1 + 1];
2078 				Xs[2] = vbuf[v2];
2079 				Ys[2] = vbuf[v2 + 1];
2080 
2081 				THs[0] = tbuf[t0] * tdim;
2082 				TVs[0] = tbuf[t0 + 1] * tdim;
2083 				THs[1] = tbuf[t1] * tdim;
2084 				TVs[1] = tbuf[t1 + 1] * tdim;
2085 				THs[2] = tbuf[t2] * tdim;
2086 				TVs[2] = tbuf[t2 + 1] * tdim;
2087 
2088 				var faceArea = (Xs[1] - Xs[0]) * (Ys[2] - Ys[0]) - (Ys[1] - Ys[0]) * (Xs[2] - Xs[0]);
2089 				if(faceArea < 0)
2090 					faceArea = -faceArea;
2091 				faceArea += 1;
2092 				var texArea = (THs[1] - THs[0]) * (TVs[2] - TVs[0]) - (TVs[1] -  TVs[0]) * (THs[2] - THs[0]);
2093 				if(texArea < 0)
2094 					texArea = -texArea;
2095 				var mipRatio = texArea / faceArea;
2096 
2097 				var level = 0;
2098 				if(mipRatio < mipentries[1])
2099 					level = 0;
2100 				else if(mipRatio >= mipentries[mipentries.length - 1]) {
2101 					level = mipentries.length - 1;
2102 					tdim = 1;
2103 				}
2104 				else {
2105 					while(mipRatio >= mipentries[level+1]) {
2106 						level++;
2107 						tdim /= 2;
2108 					}
2109 				}
2110 
2111 				tdata = mipmaps[level];
2112 				tbound = tdim - 1;
2113 			}
2114 
2115 			do {
2116 				v2 = ibuf[j] * 3;
2117 				t2 = tibuf[j] * 2;
2118 				j++;
2119 
2120 				Xs[0] = ~~(vbuf[v0]     + 0.5);
2121 				Ys[0] = ~~(vbuf[v0 + 1] + 0.5);
2122 				Zs[0] = vbuf[v0 + 2];
2123 				Xs[1] = ~~(vbuf[v1]     + 0.5);
2124 				Ys[1] = ~~(vbuf[v1 + 1] + 0.5);
2125 				Zs[1] = vbuf[v1 + 2];
2126 				Xs[2] = ~~(vbuf[v2]     + 0.5);
2127 				Ys[2] = ~~(vbuf[v2 + 1] + 0.5);
2128 				Zs[2] = vbuf[v2 + 2];
2129 
2130 				THs[0] = tbuf[t0] * tdim;
2131 				TVs[0] = tbuf[t0 + 1] * tdim;
2132 				THs[1] = tbuf[t1] * tdim;
2133 				TVs[1] = tbuf[t1 + 1] * tdim;
2134 				THs[2] = tbuf[t2] * tdim;
2135 				TVs[2] = tbuf[t2 + 1] * tdim;
2136 
2137 				var high = Ys[0] < Ys[1] ? 0 : 1;
2138 				high = Ys[high] < Ys[2] ? high : 2;
2139 				var low = Ys[0] > Ys[1] ? 0 : 1;
2140 				low = Ys[low] > Ys[2] ? low : 2;
2141 				var mid = 3 - low - high;
2142 
2143 				if(high != low) {
2144 					var x0 = Xs[low];
2145 					var z0 = Zs[low];
2146 					var th0 = THs[low];
2147 					var tv0 = TVs[low];
2148 					var dy0 = Ys[low] - Ys[high];
2149 					dy0 = dy0 != 0 ? dy0 : 1;
2150 					var xStep0 = (Xs[low] - Xs[high]) / dy0;
2151 					var zStep0 = (Zs[low] - Zs[high]) / dy0;
2152 					var thStep0 = (THs[low] - THs[high]) / dy0;
2153 					var tvStep0 = (TVs[low] - TVs[high]) / dy0;
2154 
2155 					var x1 = Xs[low];
2156 					var z1 = Zs[low];
2157 					var th1 = THs[low];
2158 					var tv1 = TVs[low];
2159 					var dy1 = Ys[low] - Ys[mid];
2160 					dy1 = dy1 != 0 ? dy1 : 1;
2161 					var xStep1 = (Xs[low] - Xs[mid]) / dy1;
2162 					var zStep1 = (Zs[low] - Zs[mid]) / dy1;
2163 					var thStep1 = (THs[low] - THs[mid]) / dy1;
2164 					var tvStep1 = (TVs[low] - TVs[mid]) / dy1;
2165 
2166 					var x2 = Xs[mid];
2167 					var z2 = Zs[mid];
2168 					var th2 = THs[mid];
2169 					var tv2 = TVs[mid];
2170 					var dy2 = Ys[mid] - Ys[high];
2171 					dy2 = dy2 != 0 ? dy2 : 1;
2172 					var xStep2 = (Xs[mid] - Xs[high]) / dy2;
2173 					var zStep2 = (Zs[mid] - Zs[high]) / dy2;
2174 					var thStep2 = (THs[mid] - THs[high]) / dy2;
2175 					var tvStep2 = (TVs[mid] - TVs[high]) / dy2;
2176 
2177 					var linebase = Ys[low] * w;
2178 					for(var y=Ys[low]; y>Ys[high]; y--) {
2179 						if(y >=0 && y < h) {
2180 							var xLeft = ~~x0;
2181 							var zLeft = z0;
2182 							var thLeft = th0;
2183 							var tvLeft = tv0;
2184 							var xRight, zRight, thRight, tvRight;
2185 							if(y > Ys[mid]) {
2186 								xRight = ~~x1;
2187 								zRight = z1;
2188 								thRight = th1;
2189 								tvRight = tv1;
2190 							}
2191 							else {
2192 								xRight = ~~x2;
2193 								zRight = z2;
2194 								thRight = th2;
2195 								tvRight = tv2;
2196 							}
2197 
2198 							if(xLeft > xRight) {
2199 								var temp;
2200 								temp = xLeft;
2201 								xLeft = xRight;
2202 								xRight = temp;
2203 								temp = zLeft;
2204 								zLeft = zRight;
2205 								zRight = temp;
2206 								temp = thLeft;
2207 								thLeft = thRight;
2208 								thRight = temp;
2209 								temp = tvLeft;
2210 								tvLeft = tvRight;
2211 								tvRight = temp;
2212 							}
2213 
2214 							var zInc = (xLeft != xRight) ? ((zRight - zLeft) / (xRight - xLeft)) : 1;
2215 							var thInc = (xLeft != xRight) ? ((thRight - thLeft) / (xRight - xLeft)) : 1;
2216 							var tvInc = (xLeft != xRight) ? ((tvRight - tvLeft) / (xRight - xLeft)) : 1;
2217 
2218 							if(xLeft < 0) {
2219 								zLeft -= xLeft * zInc;
2220 								thLeft -= xLeft * thInc;
2221 								tvLeft -= xLeft * tvInc;
2222 								xLeft = 0;
2223 							}
2224 							if(xRight >= w)
2225 								xRight = w - 1;
2226 
2227 							var pix = linebase + xLeft;
2228 							if(isOpaque) {
2229 								for(var x=xLeft, z=zLeft, th=thLeft, tv=tvLeft; x<=xRight; x++, z+=zInc, th+=thInc, tv+=tvInc) {
2230 									if(z > zbuf[pix]) {
2231 										zbuf[pix] = z;
2232 										var texel = tdata[(tv & tbound) * tdim + (th & tbound)];
2233 										var rr = (((color & 0xff0000) >> 16) * ((texel & 0xff0000) >> 8));
2234 										var gg = (((color & 0xff00) >> 8) * ((texel & 0xff00) >> 8));
2235 										var bb = ((color & 0xff) * (texel & 0xff)) >> 8;
2236 										cbuf[pix] = (rr & 0xff0000) | (gg & 0xff00) | (bb & 0xff);
2237 										sbuf[pix] = id;
2238 									}
2239 									pix++;
2240 								}
2241 							}
2242 							else {
2243 								for(var x=xLeft, z=zLeft, th=thLeft, tv=tvLeft; x<xRight; x++, z+=zInc, th+=thInc, tv+=tvInc) {
2244 									if(z > zbuf[pix]) {
2245 										var foreColor = tdata[(tv & tbound) * tdim + (th & tbound)];
2246 										var backColor = cbuf[pix];
2247 										var opaci = (((foreColor >> 24) & 0xff) * (matOpacity & 0xff)) >> 8;
2248 										var rr = (((color & 0xff0000) >> 16) * ((foreColor & 0xff0000) >> 8));
2249 										var gg = (((color & 0xff00) >> 8) * ((foreColor & 0xff00) >> 8));
2250 										var bb = ((color & 0xff) * (foreColor & 0xff)) >> 8;
2251 										if(opaci > 250) {
2252 											zbuf[pix] = z;
2253 										}
2254 										else {
2255 											var trans = 255 - opaci;
2256 											rr = (rr * opaci + (backColor & 0xff0000) * trans) >> 8;
2257 											gg = (gg * opaci + (backColor & 0xff00) * trans) >> 8;
2258 											bb = (bb * opaci + (backColor & 0xff) * trans) >> 8;
2259 										}
2260 										cbuf[pix] = (rr & 0xff0000) | (gg & 0xff00) | (bb & 0xff);
2261 										sbuf[pix] = id;
2262 									}
2263 									pix++;
2264 								}
2265 							}
2266 						}
2267 
2268 						// step up to next scanline
2269 						//
2270 						x0 -= xStep0;
2271 						z0 -= zStep0;
2272 						th0 -= thStep0;
2273 						tv0 -= tvStep0;
2274 						if(y > Ys[mid]) {
2275 							x1 -= xStep1;
2276 							z1 -= zStep1;
2277 							th1 -= thStep1;
2278 							tv1 -= tvStep1;
2279 						}
2280 						else {
2281 							x2 -= xStep2;
2282 							z2 -= zStep2;
2283 							th2 -= thStep2;
2284 							tv2 -= tvStep2;
2285 						}
2286 						linebase -= w;
2287 					}
2288 				}
2289 
2290 				v1 = v2;
2291 				t1 = t2;
2292 			} while (ibuf[j] != -1);
2293 
2294 			j++;
2295 		}
2296 	}
2297 };
2298 
2299 /**
2300 	Render the given mesh as textured object. Lighting will be calculated per vertex and then interpolated between and inside scanlines.
2301 	@private
2302  */
2303 JSC3D.Viewer.prototype.renderTextureSmooth = function(mesh) {
2304 	var w = this.frameWidth;
2305 	var h = this.frameHeight;
2306 	var ibuf = mesh.indexBuffer;
2307 	var vbuf = mesh.transformedVertexBuffer;
2308 	var vnbuf = mesh.transformedVertexNormalZBuffer;
2309 	var fnbuf = mesh.transformedFaceNormalZBuffer;
2310 	var cbuf = this.colorBuffer;
2311 	var zbuf = this.zBuffer;
2312 	var sbuf = this.selectionBuffer;
2313 	var numOfFaces = mesh.faceCount;
2314 	var id = mesh.internalId;
2315 	var numOfVertices = vbuf.length / 3;
2316 	var material = mesh.material ? mesh.material : this.defaultMaterial;
2317 	var palette = material.getPalette();
2318 	var texture = mesh.texture;
2319 	var isOpaque = (material.transparency == 0) && !texture.hasTransparency;
2320 	var matOpacity = ~~((1 - material.transparency) * 255);
2321 	var tbuf = mesh.texCoordBuffer;
2322 	var tibuf = mesh.texCoordIndexBuffer;
2323 	var tdata = texture.data;
2324 	var tdim = texture.width;
2325 	var tbound = tdim - 1;
2326 	var mipmaps = texture.hasMipmap() ? texture.mipmaps : null;
2327 	var mipentries = mipmaps ? texture.mipentries : null;
2328 
2329 	// skip this mesh if it is completely transparent
2330 	if(material.transparency == 1)
2331 		return;
2332 
2333 	if(!vnbuf || vnbuf.length < numOfVertices) {
2334 		mesh.transformedVertexNormalZBuffer = new Array(numOfVertices);
2335 		vnbuf = mesh.transformedVertexNormalZBuffer;
2336 	}
2337 
2338 	if(!fnbuf || fnbuf.length < numOfFaces) {
2339 		mesh.transformedFaceNormalZBuffer = new Array(numOfFaces);
2340 		fnbuf = mesh.transformedFaceNormalZBuffer;
2341 	}
2342 
2343 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.vertexNormalBuffer, vnbuf);
2344 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.faceNormalBuffer, fnbuf);
2345 
2346 	var isDoubleSided = mesh.isDoubleSided;
2347 
2348 	var Xs = new Array(3);
2349 	var Ys = new Array(3);
2350 	var Zs = new Array(3);
2351 	var Ns = new Array(3);
2352 	var THs = new Array(3);
2353 	var TVs = new Array(3);
2354 	var i = 0, j = 0;
2355 	while(i < numOfFaces) {
2356 		var xformedFNz = fnbuf[i++];
2357 		if(isDoubleSided)
2358 			xformedFNz = xformedFNz > 0 ? xformedFNz : -xformedFNz;
2359 		if(xformedFNz < 0) {
2360 			do {
2361 			} while (ibuf[j++] != -1);
2362 		}
2363 		else {
2364 			var i0, i1, i2;
2365 			var v0, v1, v2;
2366 			var t0, t1, t2;
2367 			i0 = ibuf[j];
2368 			v0 = i0 * 3;
2369 			t0 = tibuf[j] * 2;
2370 			j++;
2371 			i1 = ibuf[j];
2372 			v1 = i1 * 3;
2373 			t1 = tibuf[j] * 2;
2374 			j++;
2375 
2376 			if(mipmaps) {
2377 				v2 = ibuf[j] * 3;
2378 				t2 = tibuf[j] * 2;
2379 
2380 				tdim = texture.width;
2381 
2382 				Xs[0] = vbuf[v0];
2383 				Ys[0] = vbuf[v0 + 1];
2384 				Xs[1] = vbuf[v1];
2385 				Ys[1] = vbuf[v1 + 1];
2386 				Xs[2] = vbuf[v2];
2387 				Ys[2] = vbuf[v2 + 1];
2388 
2389 				THs[0] = tbuf[t0] * tdim;
2390 				TVs[0] = tbuf[t0 + 1] * tdim;
2391 				THs[1] = tbuf[t1] * tdim;
2392 				TVs[1] = tbuf[t1 + 1] * tdim;
2393 				THs[2] = tbuf[t2] * tdim;
2394 				TVs[2] = tbuf[t2 + 1] * tdim;
2395 
2396 				var faceArea = (Xs[1] - Xs[0]) * (Ys[2] - Ys[0]) - (Ys[1] - Ys[0]) * (Xs[2] - Xs[0]);
2397 				if(faceArea < 0)
2398 					faceArea = -faceArea;
2399 				faceArea += 1;
2400 				var texArea = (THs[1] - THs[0]) * (TVs[2] - TVs[0]) - (TVs[1] -  TVs[0]) * (THs[2] - THs[0]);
2401 				if(texArea < 0)
2402 					texArea = -texArea;
2403 				var mipRatio = texArea / faceArea;
2404 
2405 				var level = 0;
2406 				if(mipRatio < mipentries[1])
2407 					level = 0;
2408 				else if(mipRatio >= mipentries[mipentries.length - 1]) {
2409 					level = mipentries.length - 1;
2410 					tdim = 1;
2411 				}
2412 				else {
2413 					while(mipRatio >= mipentries[level+1]) {
2414 						level++;
2415 						tdim /= 2;
2416 					}
2417 				}
2418 
2419 				tdata = mipmaps[level];
2420 				tbound = tdim - 1;
2421 			}
2422 			
2423 			do {
2424 				i2 = ibuf[j];
2425 				v2 = i2 * 3;
2426 				t2 = tibuf[j] * 2;
2427 				j++;
2428 
2429 				Xs[0] = ~~(vbuf[v0]     + 0.5);
2430 				Ys[0] = ~~(vbuf[v0 + 1] + 0.5);
2431 				Zs[0] = vbuf[v0 + 2];
2432 				Xs[1] = ~~(vbuf[v1]     + 0.5);
2433 				Ys[1] = ~~(vbuf[v1 + 1] + 0.5);
2434 				Zs[1] = vbuf[v1 + 2];
2435 				Xs[2] = ~~(vbuf[v2]     + 0.5);
2436 				Ys[2] = ~~(vbuf[v2 + 1] + 0.5);
2437 				Zs[2] = vbuf[v2 + 2];
2438 
2439 				THs[0] = tbuf[t0] * tdim;
2440 				TVs[0] = tbuf[t0 + 1] * tdim;
2441 				THs[1] = tbuf[t1] * tdim;
2442 				TVs[1] = tbuf[t1 + 1] * tdim;
2443 				THs[2] = tbuf[t2] * tdim;
2444 				TVs[2] = tbuf[t2 + 1] * tdim;
2445 
2446 				Ns[0] = vnbuf[i0];
2447 				Ns[1] = vnbuf[i1];
2448 				Ns[2] = vnbuf[i2];
2449 				if(isDoubleSided) {
2450 					if(Ns[0] < 0)
2451 						Ns[0] = -Ns[0];
2452 					if(Ns[1] < 0)
2453 						Ns[1] = -Ns[1];
2454 					if(Ns[2] < 0)
2455 						Ns[2] = -Ns[2];
2456 				}
2457 
2458 				var high = Ys[0] < Ys[1] ? 0 : 1;
2459 				high = Ys[high] < Ys[2] ? high : 2;
2460 				var low = Ys[0] > Ys[1] ? 0 : 1;
2461 				low = Ys[low] > Ys[2] ? low : 2;
2462 				var mid = 3 - low - high;
2463 
2464 				if(high != low) {
2465 					var x0 = Xs[low];
2466 					var z0 = Zs[low];
2467 					var th0 = THs[low];
2468 					var tv0 = TVs[low];
2469 					var n0 = Ns[low] * 255;
2470 					var dy0 = Ys[low] - Ys[high];
2471 					dy0 = dy0 != 0 ? dy0 : 1;
2472 					var xStep0 = (Xs[low] - Xs[high]) / dy0;
2473 					var zStep0 = (Zs[low] - Zs[high]) / dy0;
2474 					var thStep0 = (THs[low] - THs[high]) / dy0;
2475 					var tvStep0 = (TVs[low] - TVs[high]) / dy0;
2476 					var nStep0 = (Ns[low] - Ns[high]) * 255 / dy0;
2477 
2478 					var x1 = Xs[low];
2479 					var z1 = Zs[low];
2480 					var th1 = THs[low];
2481 					var tv1 = TVs[low];
2482 					var n1 = Ns[low] * 255;
2483 					var dy1 = Ys[low] - Ys[mid];
2484 					dy1 = dy1 != 0 ? dy1 : 1;
2485 					var xStep1 = (Xs[low] - Xs[mid]) / dy1;
2486 					var zStep1 = (Zs[low] - Zs[mid]) / dy1;
2487 					var thStep1 = (THs[low] - THs[mid]) / dy1;
2488 					var tvStep1 = (TVs[low] - TVs[mid]) / dy1;
2489 					var nStep1 = (Ns[low] - Ns[mid]) * 255 / dy1;
2490 
2491 					var x2 = Xs[mid];
2492 					var z2 = Zs[mid];
2493 					var th2 = THs[mid];
2494 					var tv2 = TVs[mid];
2495 					var n2 = Ns[mid] * 255;
2496 					var dy2 = Ys[mid] - Ys[high];
2497 					dy2 = dy2 != 0 ? dy2 : 1;
2498 					var xStep2 = (Xs[mid] - Xs[high]) / dy2;
2499 					var zStep2 = (Zs[mid] - Zs[high]) / dy2;
2500 					var thStep2 = (THs[mid] - THs[high]) / dy2;
2501 					var tvStep2 = (TVs[mid] - TVs[high]) / dy2;
2502 					var nStep2 = (Ns[mid] - Ns[high]) * 255 / dy2;
2503 
2504 					var linebase = Ys[low] * w;
2505 					for(var y=Ys[low]; y>Ys[high]; y--) {
2506 						if(y >=0 && y < h) {
2507 							var xLeft = ~~x0;
2508 							var zLeft = z0;
2509 							var thLeft = th0;
2510 							var tvLeft = tv0;
2511 							var nLeft = n0;
2512 							var xRight, zRight, thRight, tvRight, nRight;
2513 							if(y > Ys[mid]) {
2514 								xRight = ~~x1;
2515 								zRight = z1;
2516 								thRight = th1;
2517 								tvRight = tv1;
2518 								nRight = n1;
2519 							}
2520 							else {
2521 								xRight = ~~x2;
2522 								zRight = z2;
2523 								thRight = th2;
2524 								tvRight = tv2;
2525 								nRight = n2;
2526 							}
2527 
2528 							if(xLeft > xRight) {
2529 								var temp;
2530 								temp = xLeft;
2531 								xLeft = xRight;
2532 								xRight = temp;
2533 								temp = zLeft;
2534 								zLeft = zRight;
2535 								zRight = temp;
2536 								temp = thLeft;
2537 								thLeft = thRight;
2538 								thRight = temp;
2539 								temp = tvLeft;
2540 								tvLeft = tvRight;
2541 								tvRight = temp;
2542 								temp = nLeft;
2543 								nLeft = nRight;
2544 								nRight = temp;
2545 							}
2546 
2547 							var zInc = (xLeft != xRight) ? ((zRight - zLeft) / (xRight - xLeft)) : 1;
2548 							var thInc = (xLeft != xRight) ? ((thRight - thLeft) / (xRight - xLeft)) : 1;
2549 							var tvInc = (xLeft != xRight) ? ((tvRight - tvLeft) / (xRight - xLeft)) : 1;
2550 							var nInc = (xLeft != xRight) ? ((nRight - nLeft) / (xRight - xLeft)) : 0;
2551 
2552 							if(xLeft < 0) {
2553 								zLeft -= xLeft * zInc;
2554 								thLeft -= xLeft * thInc;
2555 								tvLeft -= xLeft * tvInc;
2556 								nLeft -= xLeft * nInc;
2557 								xLeft = 0;
2558 							}
2559 							if(xRight >= w)
2560 								xRight = w - 1;
2561 
2562 							var pix = linebase + xLeft;
2563 							if(isOpaque) {
2564 								for(var x=xLeft, z=zLeft, n=nLeft, th=thLeft, tv=tvLeft; x<=xRight; x++, z+=zInc, n+=nInc, th+=thInc, tv+=tvInc) {
2565 									if(z > zbuf[pix]) {
2566 										zbuf[pix] = z;
2567 										var color = palette[n > 0 ? (~~n) : 0];
2568 										var texel = tdata[(tv & tbound) * tdim + (th & tbound)];
2569 										var rr = (((color & 0xff0000) >> 16) * ((texel & 0xff0000) >> 8));
2570 										var gg = (((color & 0xff00) >> 8) * ((texel & 0xff00) >> 8));
2571 										var bb = ((color & 0xff) * (texel & 0xff)) >> 8;
2572 										cbuf[pix] = (rr & 0xff0000) | (gg & 0xff00) | (bb & 0xff);
2573 										sbuf[pix] = id;
2574 									}
2575 									pix++;
2576 								}
2577 							}
2578 							else {
2579 								for(var x=xLeft, z=zLeft, n=nLeft, th=thLeft, tv=tvLeft; x<xRight; x++, z+=zInc, n+=nInc, th+=thInc, tv+=tvInc) {
2580 									if(z > zbuf[pix]) {
2581 										var color = palette[n > 0 ? (~~n) : 0];
2582 										var foreColor = tdata[(tv & tbound) * tdim + (th & tbound)];
2583 										var backColor = cbuf[pix];
2584 										var opaci = (((foreColor >> 24) & 0xff) * (matOpacity & 0xff)) >> 8;
2585 										var rr = (((color & 0xff0000) >> 16) * ((foreColor & 0xff0000) >> 8));
2586 										var gg = (((color & 0xff00) >> 8) * ((foreColor & 0xff00) >> 8));
2587 										var bb = ((color & 0xff) * (foreColor & 0xff)) >> 8;
2588 										if(opaci > 250) {
2589 											zbuf[pix] = z;
2590 										}
2591 										else {
2592 											var trans = 255 - opaci;
2593 											rr = (rr * opaci + (backColor & 0xff0000) * trans) >> 8;
2594 											gg = (gg * opaci + (backColor & 0xff00) * trans) >> 8;
2595 											bb = (bb * opaci + (backColor & 0xff) * trans) >> 8;
2596 										}
2597 										cbuf[pix] = (rr & 0xff0000) | (gg & 0xff00) | (bb & 0xff);
2598 										sbuf[pix] = id;
2599 									}
2600 									pix++;
2601 								}
2602 							}
2603 						}
2604 
2605 						// step up to next scanline
2606 						//
2607 						x0 -= xStep0;
2608 						z0 -= zStep0;
2609 						th0 -= thStep0;
2610 						tv0 -= tvStep0;
2611 						n0 -= nStep0;
2612 						if(y > Ys[mid]) {
2613 							x1 -= xStep1;
2614 							z1 -= zStep1;
2615 							th1 -= thStep1;
2616 							tv1 -= tvStep1;
2617 							n1 -= nStep1;
2618 						}
2619 						else {
2620 							x2 -= xStep2;
2621 							z2 -= zStep2;
2622 							th2 -= thStep2;
2623 							tv2 -= tvStep2;
2624 							n2 -= nStep2;
2625 						}
2626 						linebase -= w;
2627 					}
2628 				}
2629 
2630 				i1 = i2;
2631 				v1 = v2;
2632 				t1 = t2;
2633 			} while (ibuf[j] != -1);
2634 
2635 			j++;
2636 		}
2637 	}
2638 };
2639 
2640 /**
2641 	Render the given mesh as solid object with sphere mapping. Lighting will be calculated per vertex and then interpolated between and inside scanlines.
2642 	@private
2643  */
2644 JSC3D.Viewer.prototype.renderSolidSphereMapped = function(mesh) {
2645 	var w = this.frameWidth;
2646 	var h = this.frameHeight;
2647 	var ibuf = mesh.indexBuffer;
2648 	var vbuf = mesh.transformedVertexBuffer;
2649 	var vnbuf = mesh.transformedVertexNormalBuffer;
2650 	var fnbuf = mesh.transformedFaceNormalZBuffer;
2651 	var cbuf = this.colorBuffer;
2652 	var zbuf = this.zBuffer;
2653 	var sbuf = this.selectionBuffer;
2654 	var numOfFaces = mesh.faceCount;
2655 	var numOfVertices = vbuf.length / 3;
2656 	var id = mesh.internalId;
2657 	var material = mesh.material ? mesh.material : this.defaultMaterial;
2658 	var palette = material.getPalette();
2659 	var sphereMap = this.sphereMap;
2660 	var sdata = sphereMap.data;
2661 	var sdim = sphereMap.width;
2662 	var sbound = sdim - 1;
2663 	var isOpaque = material.transparency == 0;
2664 	var trans = material.transparency * 255;
2665 	var opaci = 255 - trans;
2666 
2667 	// skip this mesh if it is completely transparent
2668 	if(material.transparency == 1)
2669 		return;
2670 
2671 	if(!vnbuf || vnbuf.length < numOfVertices * 3) {
2672 		mesh.transformedVertexNormalBuffer = new Array(numOfVertices * 3);
2673 		vnbuf = mesh.transformedVertexNormalBuffer;
2674 	}
2675 
2676 	if(!fnbuf || fnbuf.length < numOfFaces) {
2677 		mesh.transformedFaceNormalZBuffer = new Array(numOfFaces);
2678 		fnbuf = mesh.transformedFaceNormalZBuffer;
2679 	}
2680 
2681 	JSC3D.Math3D.transformVectors(this.rotMatrix, mesh.vertexNormalBuffer, vnbuf);
2682 	JSC3D.Math3D.transformVectorZs(this.rotMatrix, mesh.faceNormalBuffer, fnbuf);
2683 
2684 	var isDoubleSided = mesh.isDoubleSided;
2685 
2686 	var Xs = new Array(3);
2687 	var Ys = new Array(3);
2688 	var Zs = new Array(3);
2689 	var NXs = new Array(3);
2690 	var NYs = new Array(3);
2691 	var NZs = new Array(3);
2692 	var i = 0, j = 0;
2693 	while(i < numOfFaces) {
2694 		var xformedFNz = fnbuf[i++];
2695 		if(isDoubleSided)
2696 			xformedFNz = xformedFNz > 0 ? xformedFNz : -xformedFNz;
2697 		if(xformedFNz < 0) {
2698 			do {
2699 			} while (ibuf[j++] != -1);
2700 		}
2701 		else {
2702 			var v0, v1, v2;
2703 			v0 = ibuf[j++] * 3;
2704 			v1 = ibuf[j++] * 3;
2705 
2706 			do {
2707 				v2 = ibuf[j++] * 3;
2708 
2709 				Xs[0] = ~~(vbuf[v0]     + 0.5);
2710 				Ys[0] = ~~(vbuf[v0 + 1] + 0.5);
2711 				Zs[0] = vbuf[v0 + 2];
2712 				Xs[1] = ~~(vbuf[v1]     + 0.5);
2713 				Ys[1] = ~~(vbuf[v1 + 1] + 0.5);
2714 				Zs[1] = vbuf[v1 + 2];
2715 				Xs[2] = ~~(vbuf[v2]     + 0.5);
2716 				Ys[2] = ~~(vbuf[v2 + 1] + 0.5);
2717 				Zs[2] = vbuf[v2 + 2];
2718 
2719 				NXs[0] = vnbuf[v0];
2720 				NYs[0] = vnbuf[v0 + 1];
2721 				NZs[0] = vnbuf[v0 + 2];
2722 				NXs[1] = vnbuf[v1];
2723 				NYs[1] = vnbuf[v1 + 1];
2724 				NZs[1] = vnbuf[v1 + 2];
2725 				NXs[2] = vnbuf[v2];
2726 				NYs[2] = vnbuf[v2 + 1];
2727 				NZs[2] = vnbuf[v2 + 2];
2728 				if(isDoubleSided) {
2729 					if(NZs[0] < 0)
2730 						NZs[0] = -NZs[0];
2731 					if(NZs[1] < 0)
2732 						NZs[1] = -NZs[1];
2733 					if(NZs[2] < 0)
2734 						NZs[2] = -NZs[2];
2735 				}
2736 
2737 				var high = Ys[0] < Ys[1] ? 0 : 1;
2738 				high = Ys[high] < Ys[2] ? high : 2;
2739 				var low = Ys[0] > Ys[1] ? 0 : 1;
2740 				low = Ys[low] > Ys[2] ? low : 2;
2741 				var mid = 3 - low - high;
2742 
2743 				if(high != low) {
2744 					var x0 = Xs[low];
2745 					var z0 = Zs[low];
2746 					var n0 = NZs[low] * 255;
2747 					var sh0 = ((NXs[low] / 2 + 0.5) * sdim) & sbound;
2748 					var sv0 = ((0.5 - NYs[low] / 2) * sdim) & sbound;
2749 					var dy0 = Ys[low] - Ys[high];
2750 					dy0 = dy0 != 0 ? dy0 : 1;
2751 					var xStep0 = (Xs[low] - Xs[high]) / dy0;
2752 					var zStep0 = (Zs[low] - Zs[high]) / dy0;
2753 					var nStep0 = (NZs[low] - NZs[high]) * 255 / dy0;
2754 					var shStep0 = (((NXs[low] - NXs[high]) / 2) * sdim) / dy0;
2755 					var svStep0 = (((NYs[high] - NYs[low]) / 2) * sdim) / dy0;
2756 
2757 					var x1 = Xs[low];
2758 					var z1 = Zs[low];
2759 					var n1 = NZs[low] * 255;
2760 					var sh1 = ((NXs[low] / 2 + 0.5) * sdim) & sbound;
2761 					var sv1 = ((0.5 - NYs[low] / 2) * sdim) & sbound;
2762 					var dy1 = Ys[low] - Ys[mid];
2763 					dy1 = dy1 != 0 ? dy1 : 1;
2764 					var xStep1 = (Xs[low] - Xs[mid]) / dy1;
2765 					var zStep1 = (Zs[low] - Zs[mid]) / dy1;
2766 					var nStep1 = (NZs[low] - NZs[mid]) * 255 / dy1;
2767 					var shStep1 = (((NXs[low] - NXs[mid]) / 2) * sdim) / dy1;
2768 					var svStep1 = (((NYs[mid] - NYs[low]) / 2) * sdim) / dy1;
2769 
2770 					var x2 = Xs[mid];
2771 					var z2 = Zs[mid];
2772 					var n2 = NZs[mid] * 255;
2773 					var sh2 = ((NXs[mid] / 2 + 0.5) * sdim) & sbound;
2774 					var sv2 = ((0.5 - NYs[mid] / 2) * sdim) & sbound;
2775 					var dy2 = Ys[mid] - Ys[high];
2776 					dy2 = dy2 != 0 ? dy2 : 1;
2777 					var xStep2 = (Xs[mid] - Xs[high]) / dy2;
2778 					var zStep2 = (Zs[mid] - Zs[high]) / dy2;
2779 					var nStep2 = (NZs[mid] - NZs[high]) * 255 / dy2;
2780 					var shStep2 = (((NXs[mid] - NXs[high]) / 2) * sdim) / dy2;
2781 					var svStep2 = (((NYs[high] - NYs[mid]) / 2) * sdim) / dy2;
2782 
2783 					var linebase = Ys[low] * w;
2784 					for(var y=Ys[low]; y>Ys[high]; y--) {
2785 						if(y >=0 && y < h) {
2786 							var xLeft = ~~x0;
2787 							var zLeft = z0;
2788 							var nLeft = n0;
2789 							var shLeft = sh0;
2790 							var svLeft = sv0;
2791 							var xRight, zRight, nRight, shRight, svRight;
2792 							if(y > Ys[mid]) {
2793 								xRight = ~~x1;
2794 								zRight = z1;
2795 								nRight = n1;
2796 								shRight = sh1;
2797 								svRight = sv1;
2798 							}
2799 							else {
2800 								xRight = ~~x2;
2801 								zRight = z2;
2802 								nRight = n2;
2803 								shRight = sh2;
2804 								svRight = sv2;
2805 							}
2806 
2807 							if(xLeft > xRight) {
2808 								var temp;
2809 								temp = xLeft;
2810 								xLeft = xRight;
2811 								xRight = temp;
2812 								temp = zLeft;
2813 								zLeft = zRight;
2814 								zRight = temp;
2815 								temp = nLeft;
2816 								nLeft = nRight;
2817 								nRight = temp;
2818 								temp = shLeft;
2819 								shLeft = shRight;
2820 								shRight = temp;
2821 								temp = svLeft;
2822 								svLeft = svRight;
2823 								svRight = temp;
2824 							}
2825 
2826 							var zInc = (xLeft != xRight) ? ((zRight - zLeft) / (xRight - xLeft)) : 1;
2827 							var nInc = (xLeft != xRight) ? ((nRight - nLeft) / (xRight - xLeft)) : 1;
2828 							var shInc = (xLeft != xRight) ? ((shRight - shLeft) / (xRight - xLeft)) : 1;
2829 							var svInc = (xLeft != xRight) ? ((svRight - svLeft) / (xRight - xLeft)) : 1;
2830 							if(xLeft < 0) {
2831 								zLeft -= xLeft * zInc;
2832 								nLeft -= xLeft * nInc;
2833 								shLeft -= shLeft * shInc;
2834 								svLeft -= svLeft * svInc;
2835 								xLeft = 0;
2836 							}
2837 							if(xRight >= w) {
2838 								xRight = w - 1;
2839 							}
2840 							var pix = linebase + xLeft;
2841 							if(isOpaque) {
2842 								for(var x=xLeft, z=zLeft, n=nLeft, sh=shLeft, sv=svLeft; x<=xRight; x++, z+=zInc, n+=nInc, sh+=shInc, sv+=svInc) {
2843 									if(z > zbuf[pix]) {
2844 										zbuf[pix] = z;
2845 										var color = palette[n > 0 ? (~~n) : 0];
2846 										var stexel = sdata[(sv & sbound) * sdim + (sh & sbound)];
2847 										var rr = (((color & 0xff0000) >> 16) * ((stexel & 0xff0000) >> 8));
2848 										var gg = (((color & 0xff00) >> 8) * ((stexel & 0xff00) >> 8));
2849 										var bb = ((color & 0xff) * (stexel & 0xff)) >> 8;
2850 										cbuf[pix] = (rr & 0xff0000) | (gg & 0xff00) | (bb & 0xff);
2851 										sbuf[pix] = id;
2852 									}
2853 									pix++;
2854 								}
2855 							}
2856 							else {
2857 								for(var x=xLeft, z=zLeft, n=nLeft, sh=shLeft, sv=svLeft; x<xRight; x++, z+=zInc, n+=nInc, sh+=shInc, sv+=svInc) {
2858 									if(z > zbuf[pix]) {
2859 										var color = palette[n > 0 ? (~~n) : 0];
2860 										var foreColor = sdata[(sv & sbound) * sdim + (sh & sbound)];
2861 										var backColor = cbuf[pix];										
2862 										var rr = (((color & 0xff0000) >> 16) * ((foreColor & 0xff0000) >> 8));
2863 										var gg = (((color & 0xff00) >> 8) * ((foreColor & 0xff00) >> 8));
2864 										var bb = ((color & 0xff) * (foreColor & 0xff)) >> 8;
2865 										rr = (rr * opaci + (backColor & 0xff0000) * trans) >> 8;
2866 										gg = (gg * opaci + (backColor & 0xff00) * trans) >> 8;
2867 										bb = (bb * opaci + (backColor & 0xff) * trans) >> 8;
2868 										cbuf[pix] = (rr & 0xff0000) | (gg & 0xff00) | (bb & 0xff);
2869 										sbuf[pix] = id;
2870 									}
2871 									pix++;
2872 								}
2873 							}
2874 						}
2875 
2876 						// step up to next scanline
2877 						//
2878 						x0 -= xStep0;
2879 						z0 -= zStep0;
2880 						n0 -= nStep0;
2881 						sh0 -= shStep0;
2882 						sv0 -= svStep0;
2883 						if(y > Ys[mid]) {
2884 							x1 -= xStep1;
2885 							z1 -= zStep1;
2886 							n1 -= nStep1;
2887 							sh1 -= shStep1;
2888 							sv1 -= svStep1;
2889 						}
2890 						else {
2891 							x2 -= xStep2;
2892 							z2 -= zStep2;
2893 							n2 -= nStep2;
2894 							sh2 -= shStep2;
2895 							sv2 -= svStep2;
2896 						}
2897 						linebase -= w;
2898 					}
2899 				}
2900 
2901 				v1 = v2;
2902 			} while (ibuf[j] != -1);
2903 
2904 			j++;
2905 		}
2906 	}
2907 };
2908 
2909 JSC3D.Viewer.prototype.params = null;
2910 JSC3D.Viewer.prototype.canvas = null;
2911 JSC3D.Viewer.prototype.ctx = null;
2912 JSC3D.Viewer.prototype.canvasData = null;
2913 JSC3D.Viewer.prototype.bkgColorBuffer = null;
2914 JSC3D.Viewer.prototype.colorBuffer = null;
2915 JSC3D.Viewer.prototype.zBuffer = null;
2916 JSC3D.Viewer.prototype.selectionBuffer = null;
2917 JSC3D.Viewer.prototype.frameWidth = 0;
2918 JSC3D.Viewer.prototype.frameHeight = 0;
2919 JSC3D.Viewer.prototype.scene = null;
2920 JSC3D.Viewer.prototype.defaultMaterial = null;
2921 JSC3D.Viewer.prototype.sphereMap = null;
2922 JSC3D.Viewer.prototype.isLoaded = false;
2923 JSC3D.Viewer.prototype.isFailed = false;
2924 JSC3D.Viewer.prototype.errorMsg = '';
2925 JSC3D.Viewer.prototype.needUpdate = false;
2926 JSC3D.Viewer.prototype.needRepaint = false;
2927 JSC3D.Viewer.prototype.initRotX = 0;
2928 JSC3D.Viewer.prototype.initRotY = 0;
2929 JSC3D.Viewer.prototype.initRotZ = 0;
2930 JSC3D.Viewer.prototype.zoomFactor = 1;
2931 JSC3D.Viewer.prototype.rotMatrix = null;
2932 JSC3D.Viewer.prototype.transformMatrix = null;
2933 JSC3D.Viewer.prototype.sceneUrl = '';
2934 JSC3D.Viewer.prototype.modelColor = 0xcaa618;
2935 JSC3D.Viewer.prototype.bkgColor1 = 0xffffff;
2936 JSC3D.Viewer.prototype.bkgColor2 = 0xffff80;
2937 JSC3D.Viewer.prototype.renderMode = 'flat';
2938 JSC3D.Viewer.prototype.definition = 'standard';
2939 JSC3D.Viewer.prototype.isMipMappingOn = false;
2940 JSC3D.Viewer.prototype.sphereMapUrl = '';
2941 JSC3D.Viewer.prototype.buttonStates = null;
2942 JSC3D.Viewer.prototype.keyStates = null;
2943 JSC3D.Viewer.prototype.mouseX = 0;
2944 JSC3D.Viewer.prototype.mouseY = 0;
2945 JSC3D.Viewer.prototype.onmousedown = null;
2946 JSC3D.Viewer.prototype.onmouseup = null;
2947 JSC3D.Viewer.prototype.onmousemove = null;
2948 JSC3D.Viewer.prototype.beforeupdate = null;
2949 JSC3D.Viewer.prototype.afterupdate = null;
2950 JSC3D.Viewer.prototype.mouseUsage = 'default';
2951 JSC3D.Viewer.prototype.isDefaultInputHandlerEnabled = false;
2952 
2953 
2954 /**
2955 	@class PickInfo
2956 
2957 	PickInfo is used as the return value of JSC3D.Viewer's pick() method, holding picking values at a given position
2958 	on the canvas.
2959  */
2960 JSC3D.PickInfo = function() {
2961 	this.canvasX = 0;
2962 	this.canvasY = 0;
2963 	this.depth = -Infinity;
2964 	this.mesh = null;
2965 };
2966 
2967 
2968 /**
2969 	@class Scene
2970 
2971 	This class implements scene that contains a group of meshes that forms the world. 
2972  */
2973 JSC3D.Scene = function(name) {
2974 	this.name = name || '';
2975 	this.aabb = null;
2976 	this.children = [];
2977 	this.maxChildId = 1;
2978 };
2979 
2980 /**
2981 	Initialize the scene.
2982  */
2983 JSC3D.Scene.prototype.init = function() {
2984 	if(this.isEmpty())
2985 		return;
2986 
2987 	for(var i=0; i<this.children.length; i++)
2988 		this.children[i].init();
2989 
2990 	if(!this.aabb) {
2991 		this.aabb = new JSC3D.AABB;
2992 		this.calcAABB();
2993 	}
2994 };
2995 
2996 /**
2997 	See if the scene is empty.
2998 	@returns {Boolean} true if it contains no meshes; false if it has any.
2999  */
3000 JSC3D.Scene.prototype.isEmpty = function() {
3001 	return (this.children.length == 0);
3002 };
3003 
3004 /**
3005 	Add a mesh to the scene.
3006 	@param {JSC3D.Mesh} mesh the mesh to be added.
3007  */
3008 JSC3D.Scene.prototype.addChild = function(mesh) {
3009 	mesh.internalId = this.maxChildId++;
3010 	this.children.push(mesh);
3011 };
3012 
3013 /**
3014 	Remove a mesh from the scene.
3015 	@param {JSC3D.Mesh} mesh the mesh to be added.
3016  */
3017 JSC3D.Scene.prototype.removeChild = function(mesh) {
3018 	for(var i=0; i<this.children.length; i++) {
3019 		if(this.children[i] == mesh) {
3020 			this.children.splice(i, 1);
3021 			break;
3022 		}
3023 	}
3024 };
3025 
3026 /**
3027 	Get all meshes in the scene.
3028 	@returns {Array} meshes as an array.
3029  */
3030 JSC3D.Scene.prototype.getChildren = function() {
3031 	return this.children;
3032 };
3033 
3034 /**
3035 	Calculate AABB of the scene.
3036 	@private
3037  */
3038 JSC3D.Scene.prototype.calcAABB = function() {
3039 	this.aabb.minX = this.aabb.minY = this.aabb.minZ = Number.MAX_VALUE;
3040 	this.aabb.maxX = this.aabb.maxY = this.aabb.maxZ = -Number.MAX_VALUE;
3041 	for(var i=0; i<this.children.length; i++) {
3042 		var child = this.children[i];
3043 		if(!child.isTrivial()) {
3044 			var minX = child.aabb.minX;
3045 			var minY = child.aabb.minY;
3046 			var minZ = child.aabb.minZ;
3047 			var maxX = child.aabb.maxX;
3048 			var maxY = child.aabb.maxY;
3049 			var maxZ = child.aabb.maxZ;
3050 			if(this.aabb.minX > minX)
3051 				this.aabb.minX = minX;
3052 			if(this.aabb.minY > minY)
3053 				this.aabb.minY = minY;
3054 			if(this.aabb.minZ > minZ)
3055 				this.aabb.minZ = minZ;
3056 			if(this.aabb.maxX < maxX)
3057 				this.aabb.maxX = maxX;
3058 			if(this.aabb.maxY < maxY)
3059 				this.aabb.maxY = maxY;
3060 			if(this.aabb.maxZ < maxZ)
3061 				this.aabb.maxZ = maxZ;
3062 		}
3063 	}
3064 };
3065 
3066 JSC3D.Scene.prototype.name = '';
3067 JSC3D.Scene.prototype.aabb = null;
3068 JSC3D.Scene.prototype.children = null;
3069 JSC3D.Scene.prototype.maxChildId = 1;
3070 
3071 
3072 /**
3073 	@class Mesh
3074 
3075 	This class implements mesh that is used as an expression of 3D object and the basic primitive for rendering. <br />
3076 	A mesh basically consists of a sequence of faces, and optioanlly a material, a texture mapping and other attributes and metadata.<br />
3077 	A face consists of 3 or more coplanary vertex that should be descript in counter-clockwise order.<br />
3078 	A texture mapping includes a valid texture object with a sequence of texture coordinats specified per vertex.<br />
3079  */
3080 JSC3D.Mesh = function(name, visible, material, texture, isDoubleSided, isEnvironmentCast, coordBuffer, indexBuffer, texCoordBuffer, texCoordIndexBuffer) {
3081 	this.name = name || '';
3082 	this.metadata = '';
3083 	this.visible = (visible != undefined) ? visible : true;
3084 	this.aabb = null;
3085 	this.vertexBuffer = coordBuffer || null;
3086 	this.indexBuffer = indexBuffer || null;
3087 	this.vertexNormalBuffer = null;
3088 	this.faceNormalBuffer = null;
3089 	this.material = material || null;
3090 	this.texture = texture || null;
3091 	this.faceCount = 0;
3092 	this.isDoubleSided = isDoubleSided || false;
3093 	this.isEnvironmentCast = isEnvironmentCast || false;
3094 	this.internalId = 0;
3095 	this.texCoordBuffer = texCoordBuffer || null;
3096 	this.texCoordIndexBuffer = texCoordIndexBuffer || null;
3097 	this.transformedVertexBuffer = null;
3098 	this.transformedVertexNormalZBuffer = null;
3099 	this.transformedFaceNormalZBuffer = null;
3100 	this.transformedVertexNormalBuffer = null;
3101 };
3102 
3103 /**
3104 	Initialize the mesh.
3105  */
3106 JSC3D.Mesh.prototype.init = function() {
3107 	if(this.isTrivial()) {
3108 		return;
3109 	}
3110 
3111 	if(this.faceCount == 0) {
3112 		this.calcFaceCount();
3113 		if(this.faceCount == 0)
3114 			return;
3115 	}
3116 
3117 	if(!this.aabb) {
3118 		this.aabb = new JSC3D.AABB;
3119 		this.calcAABB();
3120 	}
3121 
3122 	if(!this.faceNormalBuffer) {
3123 		this.faceNormalBuffer = new Array(this.faceCount * 3);
3124 		this.calcFaceNormals();
3125 	}
3126 
3127 	if(!this.vertexNormalBuffer) {
3128 		this.vertexNormalBuffer = new Array(this.vertexBuffer.length);
3129 		this.calcVertexNormals();
3130 	}
3131 
3132 	this.normalizeFaceNormals();
3133 
3134 	this.transformedVertexBuffer = new Array(this.vertexBuffer.length);
3135 };
3136 
3137 /**
3138 	See if the mesh is a trivial mesh. A trivial mesh should be omited in any calculations and rendering.
3139 	@returns {Boolean} true if it is trivial; false if not.
3140  */
3141 JSC3D.Mesh.prototype.isTrivial = function() {
3142 	return ( !this.vertexBuffer || this.vertexBuffer.length < 3 || 
3143 			 !this.indexBuffer || this.indexBuffer.length < 3 );
3144 };
3145 
3146 /**
3147 	Set material for the mesh.
3148 	@param {JSC3D.Material} material the material object.
3149  */
3150 JSC3D.Mesh.prototype.setMaterial = function(material) {
3151 	this.material = material;
3152 };
3153 
3154 /**
3155 	Set texture for the mesh.
3156 	@param {JSC3D.Texture} texture the texture object.
3157  */
3158 JSC3D.Mesh.prototype.setTexture = function(texture) {
3159 	this.texture = texture;
3160 };
3161 
3162 /**
3163 	See if the mesh has valid texture mapping.
3164 	@returns {Boolean} true if it has valid texture mapping; false if not.
3165  */
3166 JSC3D.Mesh.prototype.hasTexture = function() {
3167 	return ( (this.texCoordBuffer != null) && (this.texCoordBuffer.length >= 2) && 
3168 			 (this.texCoordIndexBuffer != null) && (this.texCoordIndexBuffer.length >= 3) && 
3169 			 (this.texCoordIndexBuffer.length >= this.indexBuffer.length) && 
3170 			 (this.texture != null) && this.texture.hasData() );
3171 };
3172 
3173 /**
3174 	Calculate count of faces.
3175 	@private
3176  */
3177 JSC3D.Mesh.prototype.calcFaceCount = function() {
3178 	this.faceCount = 0;
3179 
3180 	var ibuf = this.indexBuffer;
3181 	if(ibuf[ibuf.length - 1] != -1)
3182 		ibuf.push(-1);
3183 
3184 	for(var i=0; i<ibuf.length; i++) {
3185 		if(ibuf[i] == -1)
3186 			this.faceCount++;
3187 	}
3188 };
3189 
3190 /**
3191 	Calculate AABB of the mesh.
3192 	@private
3193  */
3194 JSC3D.Mesh.prototype.calcAABB = function() {
3195 	var minX = minY = minZ = Number.MAX_VALUE;
3196 	var maxX = maxY = maxZ = -Number.MAX_VALUE;
3197 
3198 	var vbuf = this.vertexBuffer;
3199 	for(var i=0; i<vbuf.length; i+=3) {
3200 		var x = vbuf[i];
3201 		var y = vbuf[i + 1];
3202 		var z = vbuf[i + 2];
3203 
3204 		if(x < minX)
3205 			minX = x;
3206 		if(x > maxX)
3207 			maxX = x;
3208 		if(y < minY)
3209 			minY = y;
3210 		if(y > maxY)
3211 			maxY = y;
3212 		if(z < minZ)
3213 			minZ = z;
3214 		if(z > maxZ)
3215 			maxZ = z;
3216 	}
3217 
3218 	this.aabb.minX = minX;
3219 	this.aabb.minY = minY;
3220 	this.aabb.minZ = minZ;
3221 	this.aabb.maxX = maxX;
3222 	this.aabb.maxY = maxY;
3223 	this.aabb.maxZ = maxZ;
3224 };
3225 
3226 /**
3227 	Calculate per face normals. The reault remain un-normalized for later vertex normal calculations.
3228 	@private
3229  */
3230 JSC3D.Mesh.prototype.calcFaceNormals = function() {
3231 	var vbuf = this.vertexBuffer;
3232 	var ibuf = this.indexBuffer;
3233 	var nbuf = this.faceNormalBuffer;
3234 	var i = 0, j = 0;
3235 	while(i < ibuf.length) {
3236 		var index = ibuf[i++] * 3;
3237 		var x0 = vbuf[index];
3238 		var y0 = vbuf[index + 1];
3239 		var z0 = vbuf[index + 2];
3240 
3241 		index = ibuf[i++] * 3;
3242 		var x1 = vbuf[index];
3243 		var y1 = vbuf[index + 1];
3244 		var z1 = vbuf[index + 2];
3245 
3246 		index = ibuf[i++] * 3;
3247 		var x2 = vbuf[index];
3248 		var y2 = vbuf[index + 1];
3249 		var z2 = vbuf[index + 2];
3250 
3251 		var dx1 = x1 - x0;
3252 		var dy1 = y1 - y0;
3253 		var dz1 = z1 - z0;
3254 		var dx2 = x2 - x0;
3255 		var dy2 = y2 - y0;
3256 		var dz2 = z2 - z0;
3257 
3258 		var nx = dy1 * dz2 - dz1 * dy2;
3259 		var ny = dz1 * dx2 - dx1 * dz2;
3260 		var nz = dx1 * dy2 - dy1 * dx2;
3261 
3262 		nbuf[j++] = nx;
3263 		nbuf[j++] = ny;
3264 		nbuf[j++] = nz;
3265 
3266 		do {
3267 		} while (ibuf[i++] != -1);
3268 	}
3269 };
3270 
3271 /**
3272 	Calculate per vertex normals.
3273 	@private
3274  */
3275 JSC3D.Mesh.prototype.calcVertexNormals = function() {
3276 	if(!this.faceNormalBuffer) {
3277 		this.faceNormalBuffer = new Array(this.faceCount * 3);
3278 		this.calcFaceNormals();
3279 	}
3280 
3281 	var vbuf = this.vertexBuffer;
3282 	var ibuf = this.indexBuffer;
3283 	var fnbuf = this.faceNormalBuffer;
3284 	var vnbuf = this.vertexNormalBuffer;
3285 	for(var i=0; i<vnbuf.length; i++) {
3286 		vnbuf[i] = 0;
3287 	}
3288 
3289 	var numOfVertices = vbuf.length / 3;
3290 
3291 	var i = 0, j = 0, k = 0;
3292 	while(i < ibuf.length) {
3293 		k = ibuf[i++];
3294 		if(k == -1) {
3295 			j += 3;
3296 		}
3297 		else {
3298 			var index = k * 3;
3299 			vnbuf[index    ] += fnbuf[j];
3300 			vnbuf[index + 1] += fnbuf[j + 1];
3301 			vnbuf[index + 2] += fnbuf[j + 2];
3302 		}
3303 	}
3304 
3305 	for(var i=0, j=0; i<vnbuf.length; i+=3, j++) {
3306 		var nx = vnbuf[i];
3307 		var ny = vnbuf[i + 1];
3308 		var nz = vnbuf[i + 2];
3309 		var len = Math.sqrt(nx * nx + ny * ny + nz * nz);
3310 		if(len > 0) {
3311 			nx /= len;
3312 			ny /= len;
3313 			nz /= len;
3314 		}
3315 
3316 		vnbuf[i    ] = nx;
3317 		vnbuf[i + 1] = ny;
3318 		vnbuf[i + 2] = nz;
3319 	}
3320 };
3321 
3322 /**
3323 	Normalize face normals.
3324 	@private
3325  */
3326 JSC3D.Mesh.prototype.normalizeFaceNormals = function() {
3327 	var nbuf = this.faceNormalBuffer;
3328 
3329 	for(var i=0; i<nbuf.length; i+=3) {
3330 		var nx = nbuf[i];
3331 		var ny = nbuf[i + 1];
3332 		var nz = nbuf[i + 2];
3333 		var len = Math.sqrt(nx * nx + ny * ny + nz * nz);
3334 		if(len > 0) {
3335 			nx /= len;
3336 			ny /= len;
3337 			nz /= len;
3338 		}
3339 
3340 		nbuf[i    ] = nx;
3341 		nbuf[i + 1] = ny;
3342 		nbuf[i + 2] = nz;
3343 	}
3344 };
3345 
3346 JSC3D.Mesh.prototype.checkValid = function() {
3347 	//TODO: not implemented yet
3348 };
3349 
3350 JSC3D.Mesh.prototype.name = '';
3351 JSC3D.Mesh.prototype.metadata = '';
3352 JSC3D.Mesh.prototype.visible = false;
3353 JSC3D.Mesh.prototype.aabb = null;
3354 JSC3D.Mesh.prototype.vertexBuffer = null;
3355 JSC3D.Mesh.prototype.indexBuffer = null;
3356 JSC3D.Mesh.prototype.vertexNormalBuffer = null;
3357 JSC3D.Mesh.prototype.faceNormalBuffer = null;
3358 JSC3D.Mesh.prototype.texCoordBuffer = null;
3359 JSC3D.Mesh.prototype.texCoordIndexBuffer = null;
3360 JSC3D.Mesh.prototype.material = null;
3361 JSC3D.Mesh.prototype.texture = null;
3362 JSC3D.Mesh.prototype.faceCount = 0;
3363 JSC3D.Mesh.prototype.isDoubleSided = false;
3364 JSC3D.Mesh.prototype.isEnvironmentCast = false;
3365 JSC3D.Mesh.prototype.internalId = 0;
3366 JSC3D.Mesh.prototype.transformedVertexBuffer = null;
3367 JSC3D.Mesh.prototype.transformedVertexNormalZBuffer = null;
3368 JSC3D.Mesh.prototype.transformedFaceNormalZBuffer = null;
3369 JSC3D.Mesh.prototype.transformedVertexNormalBuffer = null;
3370 
3371 
3372 /**
3373 	@class Material
3374 
3375 	This class implements material which describes the feel and look of a mesh.
3376  */
3377 JSC3D.Material = function(name, ambientColor, diffuseColor, transparency, simulateSpecular) {
3378 	this.name = name || '';
3379 	this.ambientColor = ambientColor || 0;
3380 	this.diffuseColor = diffuseColor || 0x7f7f7f;
3381 	this.transparency = transparency || 0;
3382 	this.simulateSpecular = simulateSpecular || false;
3383 	this.palette = null;
3384 };
3385 
3386 /**
3387 	Get the palette of the material used for shadings.
3388 	@return {Array} palette of the material as an array.
3389  */
3390 JSC3D.Material.prototype.getPalette = function() {
3391 	if(!this.palette) {
3392 		this.palette = new Array(256);
3393 		this.generatePalette();
3394 	}
3395 
3396 	return this.palette;
3397 };
3398 
3399 /**
3400 	@private
3401  */
3402 JSC3D.Material.prototype.generatePalette = function() {
3403 	var ambientR = (this.ambientColor & 0xff0000) >> 16;
3404 	var ambientG = (this.ambientColor & 0xff00) >> 8;
3405 	var ambientB = this.ambientColor & 0xff;
3406 	var diffuseR = (this.diffuseColor & 0xff0000) >> 16;
3407 	var diffuseG = (this.diffuseColor & 0xff00) >> 8;
3408 	var diffuseB = this.diffuseColor & 0xff;
3409 
3410 	if(this.simulateSpecular) {
3411 		var i = 0;
3412 		while(i < 204) {
3413 			var r = ambientR + i * diffuseR / 204;
3414 			var g = ambientG + i * diffuseG / 204;
3415 			var b = ambientB + i * diffuseB / 204;
3416 			if(r > 255)
3417 				r = 255;
3418 			if(g > 255)
3419 				g = 255;
3420 			if(b > 255)
3421 				b = 255;
3422 
3423 			this.palette[i++] = r << 16 | g << 8 | b;
3424 		}
3425 
3426 		while(i < 256) {
3427 			var r = ambientR + diffuseR + (i - 204) * (255 - diffuseR) / 82;
3428 			var g = ambientG + diffuseG + (i - 204) * (255 - diffuseG) / 82;
3429 			var b = ambientB + diffuseB + (i - 204) * (255 - diffuseB) / 82;
3430 			if(r > 255)
3431 				r = 255;
3432 			if(g > 255)
3433 				g = 255;
3434 			if(b > 255)
3435 				b = 255;
3436 
3437 			this.palette[i++] = r << 16 | g << 8 | b;
3438 		}
3439 	}
3440 	else {
3441 		var i = 0;
3442 		while(i < 256) {
3443 			var r = ambientR + i * diffuseR / 256;
3444 			var g = ambientG + i * diffuseG / 256;
3445 			var b = ambientB + i * diffuseB / 256;
3446 			if(r > 255)
3447 				r = 255;
3448 			if(g > 255)
3449 				g = 255;
3450 			if(b > 255)
3451 				b = 255;
3452 
3453 			this.palette[i++] = r << 16 | g << 8 | b;
3454 		}
3455 	}
3456 };
3457 
3458 JSC3D.Material.prototype.name = '';
3459 JSC3D.Material.prototype.ambientColor = 0;
3460 JSC3D.Material.prototype.diffuseColor = 0x7f7f7f;
3461 JSC3D.Material.prototype.transparency = 0;
3462 JSC3D.Material.prototype.simulateSpecular = false;
3463 JSC3D.Material.prototype.palette = null;
3464 
3465 
3466 /**
3467 	@class Texture
3468 
3469 	This class implements texture which describes the surface details for a mesh.
3470  */
3471 JSC3D.Texture = function(name, onready) {
3472 	this.name = name || '';
3473 	this.width = 0;
3474 	this.height = 0;
3475 	this.data = null;
3476 	this.mipmaps = null;
3477 	this.mipentries = null;
3478 	this.hasTransparency = false;
3479 	this.srcUrl = '';
3480 	this.onready = (onready && typeof(onready) == 'function') ? onready : null;
3481 };
3482 
3483 /**
3484 	Load an image and extract texture data from it.
3485 	@param {String} imageUrl where to load the image.
3486 	@param {Boolean} useMipmap set true to generate mip-maps; false(default) not to generate mip-maps.
3487  */
3488 JSC3D.Texture.prototype.createFromUrl = function(imageUrl, useMipmap) {
3489 	var self = this;
3490 	var img = new Image;
3491 
3492 	img.onload = function() {
3493 		self.data = null;
3494 		self.mipmaps = null;
3495 		self.mipentries = null;
3496 		self.width = 0;
3497 		self.height = 0;
3498 		self.hasTransparency = false;
3499 		self.srcUrl = '';
3500 		self.createFromImage(this, useMipmap);
3501 		if(JSC3D.console)
3502 			JSC3D.console.logInfo('Finished loading texture image file "' + this.src + '".');
3503 	};
3504 
3505 	img.onerror = function() {
3506 		self.data = null;
3507 		self.mipmaps = null;
3508 		self.mipentries = null;
3509 		self.width = 0;
3510 		self.height = 0;
3511 		self.hasTransparency = false;
3512 		self.srcUrl = '';
3513 		if(JSC3D.console)
3514 			JSC3D.console.logWarning('Failed to load texture image file "' + this.src + '". This texture will be discarded.');
3515 	};
3516 
3517 	img.src = imageUrl;
3518 };
3519 
3520 /**
3521 	Extract texture data from an exsisting image.
3522 	@param {Image} image image as datasource of the texture.
3523 	@param {Boolean} useMipmap set true to generate mip-maps; false(default) not to generate mip-maps.
3524  */
3525 JSC3D.Texture.prototype.createFromImage = function(image, useMipmap) {
3526 	if(image.width <=0 || image.height <=0)
3527 		return;
3528 
3529 	var isCanvasClean = false;
3530 	var canvas = JSC3D.Texture.cv;
3531 	if(!canvas) {
3532 		try {
3533 			canvas = document.createElement('canvas');
3534 			JSC3D.Texture.cv = canvas;
3535 			isCanvasClean = true;
3536 		}
3537 		catch(e) {
3538 			return;
3539 		}
3540 	}
3541 
3542 	var dim = image.width > image.height ? image.width : image.height;
3543 	if(dim <= 32)
3544 		dim = 32;
3545 	else if(dim <= 64)
3546 		dim = 64;
3547 	else if(dim <= 128)
3548 		dim = 128;
3549 	else if(dim <= 256)
3550 		dim = 256;
3551 	else
3552 		dim = 512;
3553 
3554 	if(canvas.width != dim || canvas.height != dim) {
3555 		canvas.width = canvas.height = dim;
3556 		isCanvasClean = true;
3557 	}
3558 
3559 	var data;
3560 	try {
3561 		var ctx = canvas.getContext('2d');
3562 		if(!isCanvasClean)
3563 			ctx.clearRect(0, 0, dim, dim);
3564 		ctx.drawImage(image, 0, 0, dim, dim);
3565 		var imgData = ctx.getImageData(0, 0, dim, dim);
3566 		data = imgData.data;
3567 	}
3568 	catch(e) {
3569 		return;
3570 	}
3571 
3572 	var size = data.length / 4;
3573 	this.data = new Array(size);
3574 	var alpha;
3575 	for(var i=0, j=0; i<size; i++, j+=4) {
3576 		alpha = data[j + 3];
3577 		this.data[i] = alpha << 24 | data[j] << 16 | data[j+1] << 8 | data[j+2];
3578 		if(alpha < 255)
3579 			this.hasTransparency = true;
3580 	}
3581 
3582 	this.width = dim;
3583 	this.height = dim;
3584 
3585 	this.mipmaps = null;
3586 	if(useMipmap)
3587 		this.generateMipmaps();
3588 
3589 	this.srcUrl = image.src;
3590 
3591 	if(this.onready != null && (typeof this.onready) == 'function')
3592 		this.onready();
3593 };
3594 
3595 /**
3596 	See if this texture contains texel data.
3597 	@returns {Boolean} true if it has texel data; false if not.
3598  */
3599 JSC3D.Texture.prototype.hasData = function() {
3600 	return (this.data != null);
3601 };
3602 
3603 /**
3604 	Generate mip-map pyramid for the texture.
3605  */
3606 JSC3D.Texture.prototype.generateMipmaps = function() {
3607 	if(this.width <= 1 || this.data == null || this.mipmaps != null)
3608 		return;
3609 
3610 	this.mipmaps = [this.data];
3611 	this.mipentries = [1];
3612 	
3613 	var numOfMipLevels = 1 + ~~(0.1 + Math.log(this.width) * Math.LOG2E);
3614 	var dim = this.width >> 1;
3615 	for(var level=1; level<numOfMipLevels; level++) {
3616 		var map = new Array(dim * dim);
3617 		var uppermap = this.mipmaps[level - 1];
3618 		var upperdim = dim << 1;
3619 
3620 		var src = 0, dest = 0;
3621 		for(var i=0; i<dim; i++) {
3622 			for(var j=0; j<dim; j++) {
3623 				var texel0 = uppermap[src];
3624 				var texel1 = uppermap[src + 1];
3625 				var texel2 = uppermap[src + upperdim];
3626 				var texel3 = uppermap[src + upperdim + 1];
3627 				var a = ( ((texel0 & 0xff000000) >>> 2) + ((texel1 & 0xff000000) >>> 2) + ((texel2 & 0xff000000) >>> 2) + ((texel3 & 0xff000000) >>> 2) ) & 0xff000000;
3628 				var r = ( ((texel0 & 0xff0000) + (texel1 & 0xff0000) + (texel2 & 0xff0000) + (texel3 & 0xff0000)) >> 2 ) & 0xff0000;
3629 				var g = ( ((texel0 & 0xff00) + (texel1 & 0xff00) + (texel2 & 0xff00) + (texel3 & 0xff00)) >> 2 ) & 0xff00;
3630 				var b = ( ((texel0 & 0xff) + (texel1 & 0xff) + (texel2 & 0xff) + (texel3 & 0xff)) >> 2 ) & 0xff;
3631 				map[dest] = a + r + g + b;
3632 				src += 2;
3633 				dest++;
3634 			}
3635 			src += upperdim;
3636 		}
3637 
3638 		this.mipmaps.push(map);
3639 		this.mipentries.push(Math.pow(4, level));
3640 		dim = dim >> 1;
3641 	}
3642 };
3643 
3644 /**
3645 	See if this texture has mip-maps.
3646 	@returns {Boolean} true if it has mip-maps; false if not.
3647  */
3648 JSC3D.Texture.prototype.hasMipmap = function() {
3649 	return (this.mipmaps != null);
3650 };
3651 
3652 JSC3D.Texture.prototype.name = '';
3653 JSC3D.Texture.prototype.data = null;
3654 JSC3D.Texture.prototype.mipmaps = null;
3655 JSC3D.Texture.prototype.mipentries = null;
3656 JSC3D.Texture.prototype.width = 0;
3657 JSC3D.Texture.prototype.height = 0;
3658 JSC3D.Texture.prototype.hasTransparency = false;
3659 JSC3D.Texture.prototype.srcUrl = '';
3660 JSC3D.Texture.prototype.onready = null;
3661 JSC3D.Texture.cv = null;
3662 
3663 
3664 /**
3665 	@class AABB
3666 
3667 	This class implements the Axis-Aligned Bounding Box to measure spacial enclosure.
3668  */
3669 JSC3D.AABB = function() {
3670 	this.minX = this.maxX = 0;
3671 	this.minY = this.maxY = 0;
3672 	this.minZ = this.maxZ = 0;
3673 };
3674 
3675 /**
3676 	Get the center coordinates of the AABB.
3677 	@returns {Array} center coordinates as an array.
3678  */
3679 JSC3D.AABB.prototype.center = function() {
3680 	return [(this.minX + this.maxX) / 2, (this.minY + this.maxY) / 2, (this.minZ + this.maxZ) / 2];
3681 };
3682 
3683 /**
3684 	Get the length of the diagonal of the AABB.
3685 	@returns {Number} length of the diagonal.
3686  */
3687 JSC3D.AABB.prototype.lengthOfDiagonal = function() {
3688 	var xx = this.maxX - this.minX;
3689 	var yy = this.maxY - this.minY;
3690 	var zz = this.maxZ - this.minZ;
3691 	return Math.sqrt(xx * xx + yy * yy + zz * zz);
3692 };
3693 
3694 
3695 /**
3696 	@class Matrix3x4
3697 
3698 	This class implements 3x4 matrix and mass operations for 3D transformations.
3699  */
3700 JSC3D.Matrix3x4 = function() {
3701 	this.m00 = 1; this.m01 = 0; this.m02 = 0; this.m03 = 0;
3702 	this.m10 = 0; this.m11 = 1; this.m12 = 0; this.m13 = 0;
3703 	this.m20 = 0; this.m21 = 0; this.m22 = 1; this.m23 = 0;
3704 };
3705 
3706 /**
3707 	Make the matrix an identical matrix.
3708  */
3709 JSC3D.Matrix3x4.prototype.identity = function() {
3710 	this.m00 = 1; this.m01 = 0; this.m02 = 0; this.m03 = 0;
3711 	this.m10 = 0; this.m11 = 1; this.m12 = 0; this.m13 = 0;
3712 	this.m20 = 0; this.m21 = 0; this.m22 = 1; this.m23 = 0;
3713 };
3714 
3715 /**
3716 	Scale the matrix using scaling factors on each axial directions.
3717 	@param {Number} sx scaling factors on x-axis.
3718 	@param {Number} sy scaling factors on y-axis.
3719 	@param {Number} sz scaling factors on z-axis.
3720  */
3721 JSC3D.Matrix3x4.prototype.scale = function(sx, sy, sz) {
3722 	this.m00 *= sx; this.m01 *= sx; this.m02 *= sx; this.m03 *= sx;
3723 	this.m10 *= sy; this.m11 *= sy; this.m12 *= sy; this.m13 *= sy;
3724 	this.m20 *= sz; this.m21 *= sz; this.m22 *= sz; this.m23 *= sz;
3725 };
3726 
3727 /**
3728 	Translate the matrix using translations on each axial directions.
3729 	@param {Number} tx translations on x-axis.
3730 	@param {Number} ty translations on y-axis.
3731 	@param {Number} tz translations on z-axis.
3732  */
3733 JSC3D.Matrix3x4.prototype.translate = function(tx, ty, tz) {
3734 	this.m03 += tx;
3735 	this.m13 += ty;
3736 	this.m23 += tz;
3737 };
3738 
3739 /**
3740 	Rotate the matrix an arbitrary angle about the x-axis.
3741 	@param {Number} angle rotation angle in degrees.
3742  */
3743 JSC3D.Matrix3x4.prototype.rotateAboutXAxis = function(angle) {
3744 	if(angle != 0) {
3745 		angle *= Math.PI / 180;
3746 		var cosA = Math.cos(angle);
3747 		var sinA = Math.sin(angle);
3748 
3749 		var m10 = cosA * this.m10 - sinA * this.m20;
3750 		var m11 = cosA * this.m11 - sinA * this.m21;
3751 		var m12 = cosA * this.m12 - sinA * this.m22;
3752 		var m13 = cosA * this.m13 - sinA * this.m23;
3753 		var m20 = cosA * this.m20 + sinA * this.m10;
3754 		var m21 = cosA * this.m21 + sinA * this.m11;
3755 		var m22 = cosA * this.m22 + sinA * this.m12;
3756 		var m23 = cosA * this.m23 + sinA * this.m13;
3757 
3758 		this.m10 = m10; this.m11 = m11; this.m12 = m12; this.m13 = m13;
3759 		this.m20 = m20; this.m21 = m21; this.m22 = m22; this.m23 = m23;
3760 	}
3761 };
3762 
3763 /**
3764 	Rotate the matrix an arbitrary angle about the y-axis.
3765 	@param {Number} angle rotation angle in degrees.
3766  */
3767 JSC3D.Matrix3x4.prototype.rotateAboutYAxis = function(angle) {
3768 	if(angle != 0) {
3769 		angle *= Math.PI / 180;
3770 		var cosA = Math.cos(angle);
3771 		var sinA = Math.sin(angle);
3772 
3773 		var m00 = cosA * this.m00 + sinA * this.m20;
3774 		var m01 = cosA * this.m01 + sinA * this.m21;
3775 		var m02 = cosA * this.m02 + sinA * this.m22;
3776 		var m03 = cosA * this.m03 + sinA * this.m23;
3777 		var m20 = cosA * this.m20 - sinA * this.m00;
3778 		var m21 = cosA * this.m21 - sinA * this.m01;
3779 		var m22 = cosA * this.m22 - sinA * this.m02;
3780 		var m23 = cosA * this.m23 - sinA * this.m03;
3781 
3782 		this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m03 = m03;
3783 		this.m20 = m20; this.m21 = m21; this.m22 = m22; this.m23 = m23;
3784 	}
3785 };
3786 
3787 /**
3788 	Rotate the matrix an arbitrary angle about the z-axis.
3789 	@param {Number} angle rotation angle in degrees.
3790  */
3791 JSC3D.Matrix3x4.prototype.rotateAboutZAxis = function(angle) {
3792 	if(angle != 0) {
3793 		angle *= Math.PI / 180;
3794 		var cosA = Math.cos(angle);
3795 		var sinA = Math.sin(angle);
3796 
3797 		var m10 = cosA * this.m10 + sinA * this.m00;
3798 		var m11 = cosA * this.m11 + sinA * this.m01;
3799 		var m12 = cosA * this.m12 + sinA * this.m02;
3800 		var m13 = cosA * this.m13 + sinA * this.m03;
3801 		var m00 = cosA * this.m00 - sinA * this.m10;
3802 		var m01 = cosA * this.m01 - sinA * this.m11;
3803 		var m02 = cosA * this.m02 - sinA * this.m12;
3804 		var m03 = cosA * this.m03 - sinA * this.m13;
3805 
3806 		this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m03 = m03;
3807 		this.m10 = m10; this.m11 = m11; this.m12 = m12; this.m13 = m13;
3808 	}
3809 };
3810 
3811 /**
3812 	Multiply the matrix by another matrix.
3813 	@param {JSC3D.Matrix3x4} mult another matrix to be multiplied on this.
3814  */
3815 JSC3D.Matrix3x4.prototype.multiply = function(mult) {
3816 	var m00 = mult.m00 * this.m00 + mult.m01 * this.m10 + mult.m02 * this.m20;
3817 	var m01 = mult.m00 * this.m01 + mult.m01 * this.m11 + mult.m02 * this.m21;
3818 	var m02 = mult.m00 * this.m02 + mult.m01 * this.m12 + mult.m02 * this.m22;
3819 	var m03 = mult.m00 * this.m03 + mult.m01 * this.m13 + mult.m02 * this.m23 + mult.m03;
3820 	var m10 = mult.m10 * this.m00 + mult.m11 * this.m10 + mult.m12 * this.m20;
3821 	var m11 = mult.m10 * this.m01 + mult.m11 * this.m11 + mult.m12 * this.m21;
3822 	var m12 = mult.m10 * this.m02 + mult.m11 * this.m12 + mult.m12 * this.m22;
3823 	var m13 = mult.m10 * this.m03 + mult.m11 * this.m13 + mult.m12 * this.m23 + mult.m13;
3824 	var m20 = mult.m20 * this.m00 + mult.m21 * this.m10 + mult.m22 * this.m20;
3825 	var m21 = mult.m20 * this.m01 + mult.m21 * this.m11 + mult.m22 * this.m21;
3826 	var m22 = mult.m20 * this.m02 + mult.m21 * this.m12 + mult.m22 * this.m22;
3827 	var m23 = mult.m20 * this.m03 + mult.m21 * this.m13 + mult.m22 * this.m23 + mult.m23;
3828 
3829 	this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m03 = m03;
3830 	this.m10 = m10; this.m11 = m11; this.m12 = m12; this.m13 = m13;
3831 	this.m20 = m20; this.m21 = m21; this.m22 = m22; this.m23 = m23;
3832 };
3833 
3834 
3835 /**
3836 	@class Math3D
3837 
3838 	This class provides some utility methods for 3D mathematics.
3839  */
3840 JSC3D.Math3D = {
3841 
3842 	/**
3843 		Transform vectors using the given matrix.
3844 		@param {JSC3D.Matrix3x4} mat the transformation matrix.
3845 		@param {Array} vecs a batch of vectors to be transform.
3846 		@param {Array} xfvecs holds the transformed vetors.
3847 	 */
3848 	transformVectors: function(mat, vecs, xfvecs) {
3849 		for(var i=0; i<vecs.length; i+=3) {
3850 			var x = vecs[i];
3851 			var y = vecs[i + 1];
3852 			var z = vecs[i + 2];
3853 			xfvecs[i]     = mat.m00 * x + mat.m01 * y + mat.m02 * z + mat.m03;
3854 			xfvecs[i + 1] = mat.m10 * x + mat.m11 * y + mat.m12 * z + mat.m13;
3855 			xfvecs[i + 2] = mat.m20 * x + mat.m21 * y + mat.m22 * z + mat.m23;
3856 		}
3857 	},
3858 
3859 	/**
3860 		Transform vectors' z components using the given matrix.
3861 		@param {JSC3D.Matrix3x4} mat the transformation matrix.
3862 		@param {Array} vecs a batch of vectors to be transform.
3863 		@param {Array} xfveczs holds the transformed z components of the input vectors.
3864 	 */
3865 	transformVectorZs: function(mat, vecs, xfveczs) {
3866 		var num = vecs.length / 3;
3867 		var i = 0, j = 0
3868 		while(i < num) {
3869 			xfveczs[i] = mat.m20 * vecs[j] + mat.m21 * vecs[j + 1] + mat.m22 * vecs[j + 2] + mat.m23;
3870 			i++;
3871 			j += 3;
3872 		}
3873 	}
3874 };
3875 
3876 
3877 /**
3878 	@class BinaryStream
3879 	The helper class to parse data from a binary stream.
3880  */
3881 JSC3D.BinaryStream = function(data, isBigEndian) {
3882 	if(isBigEndian)
3883 		throw 'JSC3D.BinaryStream constructor failed: Big endian is not supported yet!';
3884 
3885 	this.data = data;
3886 	this.offset = 0;
3887 };
3888 
3889 /**
3890 	Get the full length (in bytes) of the stream.
3891 	@returns {Number} the length of the stream.
3892  */
3893 JSC3D.BinaryStream.prototype.size = function() {
3894 	return this.data.length;
3895 };
3896 
3897 /**
3898 	Get the current position indicator of the stream.
3899 	@returns {Number} current position in stream.
3900  */
3901 JSC3D.BinaryStream.prototype.tell = function() {
3902 	return this.offset;
3903 };
3904 
3905 /**
3906 	Set the position indicator of the stream to a new position.
3907 	@param {Number} position the new position.
3908 	@returns {Boolean} true if succeeded; false if the given position is out of range.
3909  */
3910 JSC3D.BinaryStream.prototype.seek = function(position) {
3911 	if(position < 0 || position >= this.data.length)
3912 		return false;
3913 
3914 	this.offset = position;
3915 
3916 	return true;
3917 };
3918 
3919 /**
3920 	Reset the position indicator to the beginning of the stream.
3921  */
3922 JSC3D.BinaryStream.prototype.reset = function() {
3923 	this.offset = 0;
3924 };
3925 
3926 /**
3927 	Advance the position indicator to skip a given number of bytes.
3928 	@param {Number} bytesToSkip the number of bytes to skip.
3929  */
3930 JSC3D.BinaryStream.prototype.skip = function(bytesToSkip) {
3931 	if(this.offset + bytesToSkip > this.data.length)
3932 		this.offset = this.data.length;
3933 	else
3934 		this.offset += bytesToSkip;
3935 };
3936 
3937 /**
3938 	Get count of the remaining bytes in the stream.
3939 	@returns {Number} the number of bytes from current position to the end of the stream.
3940  */
3941 JSC3D.BinaryStream.prototype.available = function() {
3942 	return this.data.length - this.offset;
3943 };
3944 
3945 /**
3946 	See if the position indicator is already at the end of the stream.
3947 	@returns {Boolean} true if the position indicator is at the end of the stream; false if not.
3948  */
3949 JSC3D.BinaryStream.prototype.eof = function() {
3950 	return !(this.offset < this.data.length);
3951 };
3952 
3953 /**
3954 	Read an 8-bits' unsigned int number.
3955 	@returns {Number} an 8-bits' unsigned int number, or NaN if any error occured.
3956  */
3957 JSC3D.BinaryStream.prototype.readUInt8 = function() {
3958 	return this.decodeInt(1, false);
3959 };
3960 
3961 /**
3962 	Read an 8-bits' signed int number.
3963 	@returns {Number} an 8-bits' signed int number, or NaN if any error occured.
3964  */
3965 JSC3D.BinaryStream.prototype.readInt8 = function() {
3966 	return this.decodeInt(1, true);
3967 };
3968 
3969 /**
3970 	Read a 16-bits' unsigned int number.
3971 	@returns {Number} a 16-bits' unsigned int number, or NaN if any error occured.
3972  */
3973 JSC3D.BinaryStream.prototype.readUInt16 = function() {
3974 	return this.decodeInt(2, false);
3975 };
3976 
3977 /**
3978 	Read a 16-bits' signed int number.
3979 	@returns {Number} a 16-bits' signed int number, or NaN if any error occured.
3980  */
3981 JSC3D.BinaryStream.prototype.readInt16 = function() {
3982 	return this.decodeInt(2, true);
3983 };
3984 
3985 /**
3986 	Read a 32-bits' unsigned int number.
3987 	@returns {Number} a 32-bits' unsigned int number, or NaN if any error occured.
3988  */
3989 JSC3D.BinaryStream.prototype.readUInt32 = function() {
3990 	return this.decodeInt(4, false);
3991 };
3992 
3993 /**
3994 	Read a 32-bits' signed int number.
3995 	@returns {Number} a 32-bits' signed int number, or NaN if any error occured.
3996  */
3997 JSC3D.BinaryStream.prototype.readInt32 = function() {
3998 	return this.decodeInt(4, true);
3999 };
4000 
4001 /**
4002 	Read a 32-bits' (IEEE 754) floating point number.
4003 	@returns {Number} a 32-bits' floating point number, or NaN if any error occured.
4004  */
4005 JSC3D.BinaryStream.prototype.readFloat32 = function() {
4006 	return this.decodeFloat(4, 23);
4007 };
4008 
4009 /**
4010 	Read a 64-bits' (IEEE 754) floating point number.
4011 	@returns {Number} a 64-bits' floating point number, or NaN if any error occured.
4012  */
4013 JSC3D.BinaryStream.prototype.readFloat64 = function() {
4014 	return this.decodeFloat(8, 52);
4015 };
4016 
4017 /**
4018 	Read a piece of the stream into a given buffer.
4019 	@param {Array} buffer the buffer to receive the result.
4020 	@param {Number} bytesToRead length of the piece to be read, in bytes.
4021 	@returns {Number} the total number of bytes that are successfully read.
4022  */
4023 JSC3D.BinaryStream.prototype.readBytes = function(buffer, bytesToRead) {
4024 	var bytesRead = bytesToRead;
4025 	if(this.offset + bytesToRead > this.data.length)
4026 		bytesRead = this.data.length - this.offset;
4027 
4028 	for(var i=0; i<bytesRead; i++) {
4029 		buffer[i] = this.data[this.offset++].charCodeAt(0) & 0xff;
4030 	}
4031 
4032 	return bytesRead;
4033 };
4034 
4035 /**
4036 	@private
4037  */
4038 JSC3D.BinaryStream.prototype.decodeInt = function(bytes, isSigned) {
4039 	if(this.offset + bytes > this.data.length) {
4040 		this.offset = this.data.length;
4041 		return NaN;
4042 	}
4043 
4044 	var rv = 0, f = 1;
4045 	for(var i=0; i<bytes; i++) {
4046 		rv += ((this.data[this.offset++].charCodeAt(0) & 0xff) * f);
4047 		f *= 256;
4048 	}
4049 
4050 	if( isSigned && (rv & Math.pow(2, bytes * 8 - 1)) )
4051 		rv -= Math.pow(2, bytes * 8);
4052 
4053 	return rv;
4054 };
4055 
4056 /**
4057 	@private
4058  */
4059 JSC3D.BinaryStream.prototype.decodeFloat = function(bytes, significandBits) {
4060 	if(this.offset + bytes > this.data.length) {
4061 		this.offset = this.data.length;
4062 		return NaN;
4063 	}
4064 
4065 	var mLen = significandBits;
4066 	var eLen = bytes * 8 - mLen - 1;
4067 	var eMax = (1 << eLen) - 1;
4068 	var eBias = eMax >> 1;
4069 
4070 	var i = bytes - 1; 
4071 	var d = -1; 
4072 	var s = this.data[this.offset + i].charCodeAt(0) & 0xff; 
4073 	i += d; 
4074 	var bits = -7;
4075 	var e = s & ((1 << (-bits)) - 1);
4076 	s >>= -bits;
4077 	bits += eLen
4078 	while(bits > 0) {
4079 		e = e * 256 + (this.data[this.offset + i].charCodeAt(0) & 0xff);
4080 		i += d;
4081 		bits -= 8;
4082 	}
4083 
4084 	var m = e & ((1 << (-bits)) - 1);
4085 	e >>= -bits;
4086 	bits += mLen;
4087 	while(bits > 0) {
4088 		m = m * 256 + (this.data[this.offset + i].charCodeAt(0) & 0xff);
4089 		i += d;
4090 		bits -= 8;
4091 	}
4092 
4093 	this.offset += bytes;
4094 
4095 	switch(e) {
4096 		case 0:		// 0 or denormalized number
4097 			e = 1 - eBias;
4098 			break;
4099 		case eMax:	// NaN or +/-Infinity
4100 			return m ? NaN : ((s ? -1 : 1) * Infinity);
4101 		default:	// normalized number
4102 			m += Math.pow(2, mLen);
4103 			e -= eBias;
4104 			break;
4105 	}
4106 
4107 	return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
4108 };
4109 
4110 
4111 /**
4112 	@class LoaderSelector
4113  */
4114 JSC3D.LoaderSelector = {
4115 
4116 	/**
4117 		Register a scene loader for a specific file format, using the file extesion name for lookup.
4118 		@param {String} fileExtName extension name for the specific file format.
4119 		@param {Function} loaderCtor constructor of the loader class.
4120 	 */
4121 	registerLoader: function(fileExtName, loaderCtor) {
4122 		if((typeof loaderCtor) == 'function') {
4123 			JSC3D.LoaderSelector.loaderTable[fileExtName] = loaderCtor;
4124 		}
4125 	},
4126 
4127 	/**
4128 		Get the proper loader for a target file format using the file extension name.
4129 		@param {String} fileExtName file extension name for the specific format.
4130 		@returns {Object} loader object for the specific format; null if not found.
4131 	 */
4132 	getLoader: function(fileExtName) {
4133 		var loaderCtor = JSC3D.LoaderSelector.loaderTable[fileExtName.toLowerCase()];
4134 		if(!loaderCtor)
4135 			return null;
4136 
4137 		var loaderInst;
4138 		try {
4139 			loaderInst = new loaderCtor();
4140 		}
4141 		catch(e) {
4142 			loaderInst = null; 
4143 		}
4144 
4145 		return loaderInst;
4146 	},
4147 
4148 	loaderTable: {}
4149 };
4150 
4151 
4152 /**
4153 	@class ObjLoader
4154 
4155 	This class implements a scene loader from a wavefront obj file. 
4156  */
4157 JSC3D.ObjLoader = function(onload, onerror, onprogress, onresource) {
4158 	this.onload = (onload && typeof(onload) == 'function') ? onload : null;
4159 	this.onerror = (onerror && typeof(onerror) == 'function') ? onerror : null;
4160 	this.onprogress = (onprogress && typeof(onprogress) == 'function') ? onprogress : null;
4161 	this.onresource = (onresource && typeof(onresource) == 'function') ? onresource : null;
4162 	this.requestCount = 0;
4163 };
4164 
4165 /**
4166 	Load scene from a given obj file.
4167 	@param {String} urlName a string that specifies where to fetch the obj file.
4168  */
4169 JSC3D.ObjLoader.prototype.loadFromUrl = function(urlName) {
4170 	var urlPath = '';
4171 	var fileName = urlName;
4172 
4173 	var lastSlashAt = urlName.lastIndexOf('/');
4174 	if(lastSlashAt == -1)
4175 		lastSlashAt = urlName.lastIndexOf('\\');
4176 	if(lastSlashAt != -1) {
4177 		urlPath = urlName.substring(0, lastSlashAt+1);
4178 		fileName = urlName.substring(lastSlashAt+1);
4179 	}
4180 
4181 	this.requestCount = 0;
4182 	this.loadObjFile(urlPath, fileName);
4183 };
4184 
4185 /**
4186 	Load scene from the obj file using the given url path and file name.
4187 	@private
4188  */
4189 JSC3D.ObjLoader.prototype.loadObjFile = function(urlPath, fileName) {
4190 	var urlName = urlPath + fileName;
4191 	var self = this;
4192 	var xhr = new XMLHttpRequest;
4193 	xhr.open('GET', urlName, true);
4194 
4195 	xhr.onreadystatechange = function() {
4196 		if(this.readyState == 4) {
4197 			if(this.status == 200 || this.status == 0) {
4198 				if(self.onload) {
4199 					if(self.onprogress)
4200 						self.onprogress('Loading obj file ...', 1);
4201 					if(JSC3D.console)
4202 						JSC3D.console.logInfo('Finished loading obj file "' + urlName + '".');
4203 					var scene = new JSC3D.Scene;
4204 					var mtllibs = self.parseObj(scene, this.responseText);
4205 					if(mtllibs.length > 0) {
4206 						for(var i=0; i<mtllibs.length; i++)
4207 							self.loadMtlFile(scene, urlPath, mtllibs[i]);
4208 					}
4209 					if(--self.requestCount == 0)
4210 						self.onload(scene);
4211 				}
4212 			}
4213 			else {
4214 				if(JSC3D.console)
4215 					JSC3D.console.logError('Failed to load obj file "' + urlName + '".');
4216 				if(self.onerror) {
4217 					self.requestCount--;
4218 					self.onerror('Failed to load obj file "' + urlName + '".');
4219 				}
4220 			}
4221 		}
4222 	};
4223 
4224 	if(this.onprogress) {
4225 		this.onprogress('Loading obj file ...', 0);
4226 		xhr.onprogress = function(event) {
4227 			self.onprogress('Loading obj file ...', event.position / event.totalSize);
4228 		};
4229 	}
4230 
4231 	this.requestCount++;
4232 	xhr.send();
4233 };
4234 
4235 /**
4236 	Load materials and textures from an mtl file and set them to corresponding meshes.
4237 	@private
4238  */
4239 JSC3D.ObjLoader.prototype.loadMtlFile = function(scene, urlPath, fileName) {
4240 	var urlName = urlPath + fileName;
4241 	var self = this;
4242 	var xhr = new XMLHttpRequest;
4243 	xhr.open('GET', urlName, true);
4244 
4245 	xhr.onreadystatechange = function() {
4246 		if(this.readyState == 4) {
4247 			if(this.status == 200 || this.status == 0) {
4248 				if(self.onprogress)
4249 					self.onprogress('Loading mtl file ...', 1);
4250 				if(JSC3D.console)
4251 					JSC3D.console.logInfo('Finished loading mtl file "' + urlName + '".');
4252 				var mtls = self.parseMtl(this.responseText);
4253 				var textures = {};
4254 				var meshes = scene.getChildren();
4255 				for(var i=0; i<meshes.length; i++) {
4256 					var mesh = meshes[i];
4257 					if(mesh.mtl != null && mesh.mtllib != null && mesh.mtllib == fileName) {
4258 						var mtl = mtls[mesh.mtl];
4259 						if(mtl != null) {
4260 							if(mtl.material != null)
4261 								mesh.setMaterial(mtl.material);
4262 							if(mtl.textureFileName != '') {
4263 								if(!textures[mtl.textureFileName])
4264 									textures[mtl.textureFileName] = [mesh];
4265 								else
4266 									textures[mtl.textureFileName].push(mesh);
4267 							}
4268 						}
4269 					}
4270 				}
4271 				for(var textureFileName in textures)
4272 					self.setupTexture(textures[textureFileName], urlPath + textureFileName);
4273 			}
4274 			else {
4275 				//TODO: when failed to load an mtl file ...
4276 				if(JSC3D.console)
4277 					JSC3D.console.logWarning('Failed to load mtl file "' + urlName + '". A default material will be applied.');
4278 			}
4279 			if(--self.requestCount == 0)
4280 				self.onload(scene);
4281 		}
4282 	};
4283 
4284 	if(this.onprogress) {
4285 		this.onprogress('Loading mtl file ...', 0);
4286 		xhr.onprogress = function(event) {
4287 			self.onprogress('Loading mtl file ...', event.position / event.totalSize);
4288 		};
4289 	}
4290 
4291 	this.requestCount++;
4292 	xhr.send();
4293 };
4294 
4295 /**
4296 	Parse contents of the obj file, generating the scene and returning all required mtllibs. 
4297 	@private
4298  */
4299 JSC3D.ObjLoader.prototype.parseObj = function(scene, data) {
4300 	var meshes = {};
4301 	var mtllibs = [];
4302 	var namePrefix = 'obj-';
4303 	var meshIndex = 0;
4304 	var curMesh = null;
4305 	var curMtllibName = '';
4306 	var curMtlName = '';
4307 
4308 	var tempVertexBuffer = [];		// temporary buffer as container for all vertices
4309 	var tempTexCoordBuffer = [];	// temporary buffer as container for all vertex texture coords
4310 
4311 	// create a default mesh to hold all faces that are not associated with any mtl.
4312 	var defaultMeshName = namePrefix + meshIndex++;
4313 	var defaultMesh = new JSC3D.Mesh;
4314 	defaultMesh.name = defaultMeshName;
4315 	defaultMesh.indexBuffer = [];
4316 	meshes['nomtl'] = defaultMesh;
4317 	curMesh = defaultMesh;
4318 
4319 	var lines = data.split("\n");
4320 	for(var i=0; i<lines.length; i++) {
4321 		var line = lines[i];
4322 		var tokens = line.split(/[ \t]+/);
4323 		if(tokens.length > 0) {
4324 			var keyword = tokens[0];
4325 			switch(keyword) {
4326 			case 'v':
4327 				if(tokens.length > 3) {
4328 					for(var j=1; j<4; j++) {
4329 						tempVertexBuffer.push( parseFloat(tokens[j]) );
4330 					}
4331 				}
4332 				break;
4333 			case 'vn':
4334 				// ignore vertex normals
4335 				break;
4336 			case 'vt':
4337 				if(tokens.length > 2) {
4338 					tempTexCoordBuffer.push( parseFloat(tokens[1]) );
4339 					tempTexCoordBuffer.push( 1 - parseFloat(tokens[2]) );
4340 				}
4341 				break;
4342 			case 'f':
4343 				if(tokens.length > 3) {
4344 					for(var j=1; j<tokens.length; j++) {
4345 						var refs = tokens[j].split('/');
4346 						curMesh.indexBuffer.push( parseInt(refs[0]) - 1 );
4347 						if(refs.length > 1 && refs[1] != '') {
4348 							if(!curMesh.texCoordIndexBuffer)
4349 								curMesh.texCoordIndexBuffer = [];
4350 							curMesh.texCoordIndexBuffer.push( parseInt(refs[1]) - 1 );
4351 						}
4352 					}
4353 					curMesh.indexBuffer.push(-1);				// mark the end of vertex index sequence for the face
4354 					if(curMesh.texCoordIndexBuffer)
4355 						curMesh.texCoordIndexBuffer.push(-1);	// mark the end of vertex tex coord index sequence for the face
4356 				}
4357 				break;
4358 			case 'mtllib':
4359 				if(tokens.length > 1) {
4360 					curMtllibName = tokens[1];
4361 					mtllibs.push(curMtllibName);
4362 				}
4363 				else
4364 					curMtllibName = '';
4365 				break;
4366 			case 'usemtl':
4367 				if(tokens.length > 1 && tokens[1] != '' && curMtllibName != '') {
4368 					curMtlName = tokens[1];
4369 					var meshid = curMtllibName + '-' + curMtlName;
4370 					var mesh = meshes[meshid];
4371 					if(!mesh) {
4372 						// create a new mesh to hold faces using the same mtl
4373 						mesh = new JSC3D.Mesh;
4374 						mesh.name = namePrefix + meshIndex++;
4375 						mesh.indexBuffer = [];
4376 						mesh.mtllib = curMtllibName;
4377 						mesh.mtl = curMtlName;
4378 						meshes[meshid] = mesh;
4379 					}
4380 					curMesh = mesh;
4381 				}
4382 				else {
4383 					curMtlName = '';
4384 					curMesh = defaultMesh;
4385 				}
4386 				break;
4387 			case '#':
4388 				// ignore comments
4389 			default:
4390 				break;
4391 			}
4392 		}
4393 	}
4394 
4395 	var viBuffer = tempVertexBuffer.length >= 3 ? (new Array(tempVertexBuffer.length / 3)) : null;
4396 	var tiBuffer = tempTexCoordBuffer.length >= 2 ? (new Array(tempTexCoordBuffer.length / 2)) : null;
4397 
4398 	for(var id in meshes) {
4399 		var mesh = meshes[id];
4400 
4401 		// split vertices into the mesh, the indices are also re-calculated
4402 		if(tempVertexBuffer.length >= 3 && mesh.indexBuffer.length > 0) {
4403 			for(var i=0; i<viBuffer.length; i++)
4404 				viBuffer[i] = -1;
4405 
4406 			mesh.vertexBuffer = [];
4407 			var oldVI = 0, newVI = 0;
4408 			for(var i=0; i<mesh.indexBuffer.length; i++) {
4409 				oldVI = mesh.indexBuffer[i];
4410 				if(oldVI != -1) {
4411 					if(viBuffer[oldVI] == -1) {
4412 						var v = oldVI * 3;
4413 						mesh.vertexBuffer.push(tempVertexBuffer[v]);
4414 						mesh.vertexBuffer.push(tempVertexBuffer[v + 1]);
4415 						mesh.vertexBuffer.push(tempVertexBuffer[v + 2]);
4416 						mesh.indexBuffer[i] = newVI;
4417 						viBuffer[oldVI] = newVI;
4418 						newVI++;
4419 					}
4420 					else {
4421 						mesh.indexBuffer[i] = viBuffer[oldVI];
4422 					}
4423 				}
4424 			}
4425 		}
4426 
4427 		// split vertex texture coords into the mesh, the indices for texture coords are re-calculated as well
4428 		if(tempTexCoordBuffer.length >= 2 && mesh.texCoordIndexBuffer != null && mesh.texCoordIndexBuffer.length > 0) {
4429 			for(var i=0; i<tiBuffer.length; i++)
4430 				tiBuffer[i] = -1;
4431 
4432 			mesh.texCoordBuffer = [];
4433 			var oldTI = 0, newTI = 0;
4434 			for(var i=0; i<mesh.texCoordIndexBuffer.length; i++) {
4435 				oldTI = mesh.texCoordIndexBuffer[i];
4436 				if(oldTI != -1) {
4437 					if(tiBuffer[oldTI] == -1) {
4438 						var t = oldTI * 2;
4439 						mesh.texCoordBuffer.push(tempTexCoordBuffer[t]);
4440 						mesh.texCoordBuffer.push(tempTexCoordBuffer[t + 1]);
4441 						mesh.texCoordIndexBuffer[i] = newTI;
4442 						tiBuffer[oldTI] = newTI;
4443 						newTI++;
4444 					}
4445 					else {
4446 						mesh.texCoordIndexBuffer[i] = tiBuffer[oldTI];
4447 					}
4448 				}
4449 			}
4450 		}
4451 
4452 		// add mesh to scene
4453 		if(!mesh.isTrivial())
4454 			scene.addChild(mesh);
4455 	}
4456 
4457 	return mtllibs;
4458 };
4459 
4460 /**
4461 	Parse contents of an mtl file, returning all materials and textures defined in it.
4462 	@private
4463  */
4464 JSC3D.ObjLoader.prototype.parseMtl = function(data) {
4465 	var mtls = {};
4466 	var curMtlName = '';
4467 
4468 	var lines = data.split("\n");
4469 	for(var i=0; i<lines.length; i++) {
4470 		var line = lines[i];
4471 		var tokens = line.split(/[ \t]+/);
4472 		if(tokens.length > 0) {
4473 			var keyword = tokens[0];
4474 			switch(keyword) {
4475 			case 'newmtl':
4476 				curMtlName = tokens[1];
4477 				var mtl = {};
4478 				mtl.material = new JSC3D.Material;
4479 				mtl.textureFileName = '';
4480 				mtls[curMtlName] = mtl;
4481 				break;
4482 			case 'Ka':
4483 				/*
4484 				if(tokens.length == 4 && !isNaN(tokens[1])) {
4485 					var ambientR = (parseFloat(tokens[1]) * 255) & 0xff;
4486 					var ambientG = (parseFloat(tokens[2]) * 255) & 0xff;
4487 					var ambientB = (parseFloat(tokens[3]) * 255) & 0xff;
4488 					var mtl = mtls[curMtlName];
4489 					if(mtl != null)
4490 						mtl.material.ambientColor = (ambientR << 16) | (ambientG << 8) | ambientB;
4491 				}
4492 				*/
4493 				break;
4494 			case 'Kd':
4495 				if(tokens.length == 4 && !isNaN(tokens[1])) {
4496 					var diffuseR = (parseFloat(tokens[1]) * 255) & 0xff;
4497 					var diffuseG = (parseFloat(tokens[2]) * 255) & 0xff;
4498 					var diffuseB = (parseFloat(tokens[3]) * 255) & 0xff;
4499 					var mtl = mtls[curMtlName];
4500 					if(mtl != null)
4501 						mtl.material.diffuseColor = (diffuseR << 16) | (diffuseG << 8) | diffuseB;
4502 				}
4503 				break;
4504 			case 'Ks':
4505 				// ignore specular reflectivity definition
4506 				break;
4507 			case 'd':
4508 				if(tokens.length == 2 && !isNaN(tokens[1])) {
4509 					var opacity = parseFloat(tokens[1]);
4510 					var mtl = mtls[curMtlName];
4511 					if(mtl != null)
4512 						mtl.material.transparency = 1 - opacity;
4513 				}
4514 				break;
4515 			case 'illum':
4516 				/*
4517 				if(tokens.length == 2 && tokens[1] == '2') {
4518 					var mtl = mtls[curMtlName];
4519 					if(mtl != null)
4520 						mtl.material.simulateSpecular = true;
4521 				}
4522 				*/
4523 				break;
4524 			case 'map_Kd':
4525 				if(tokens.length == 2) {
4526 					var texFileName = tokens[1];
4527 					var mtl = mtls[curMtlName];
4528 					if(mtl != null)
4529 						mtl.textureFileName = texFileName;
4530 				}
4531 				break;
4532 			case '#':
4533 				// ignore any comments
4534 			default:
4535 				break;
4536 			}
4537 		}
4538 	}
4539 
4540 	return mtls;
4541 };
4542 
4543 /**
4544 	Asynchronously load a texture from a given url and set it to corresponding meshes when done.
4545 	@private
4546  */
4547 JSC3D.ObjLoader.prototype.setupTexture = function(meshList, textureUrlName) {
4548 	var self = this;
4549 	var texture = new JSC3D.Texture;
4550 
4551 	texture.onready = function() {
4552 		for(var i=0; i<meshList.length; i++)
4553 			meshList[i].setTexture(this);
4554 		if(self.onresource)
4555 			self.onresource(this);
4556 	};
4557 
4558 	texture.createFromUrl(textureUrlName);
4559 };
4560 
4561 JSC3D.ObjLoader.prototype.onload = null;
4562 JSC3D.ObjLoader.prototype.onerror = null;
4563 JSC3D.ObjLoader.prototype.onprogress = null;
4564 JSC3D.ObjLoader.prototype.onresource = null;
4565 JSC3D.ObjLoader.prototype.requestCount = 0;
4566 
4567 JSC3D.LoaderSelector.registerLoader('obj', JSC3D.ObjLoader);
4568 
4569 
4570 /**
4571 	@class StlLoader
4572 
4573 	This class implements a scene loader from an STL file. Both binary and ASCII STL files are supported.
4574  */
4575 JSC3D.StlLoader = function(onload, onerror, onprogress, onresource) {
4576 	this.onload = (onload && typeof(onload) == 'function') ? onload : null;
4577 	this.onerror = (onerror && typeof(onerror) == 'function') ? onerror : null;
4578 	this.onprogress = (onprogress && typeof(onprogress) == 'function') ? onprogress : null;
4579 	this.onresource = (onresource && typeof(onresource) == 'function') ? onresource : null;
4580 	this.decimalPrecision = 3;
4581 };
4582 
4583 /**
4584 	Load scene from a given STL file.
4585 	@param {String} urlName a string that specifies where to fetch the STL file.
4586  */
4587 JSC3D.StlLoader.prototype.loadFromUrl = function(urlName) {
4588 	var self = this;
4589 	var xhr = new XMLHttpRequest;
4590 	xhr.open('GET', urlName, true);
4591 	xhr.overrideMimeType('text/plain; charset=x-user-defined');
4592 
4593 	xhr.onreadystatechange = function() {
4594 		if(this.readyState == 4) {
4595 			if(this.status == 200 || this.status == 0) {
4596 				if(JSC3D.console)
4597 					JSC3D.console.logInfo('Finished loading STL file "' + urlName + '".');
4598 				if(self.onload) {
4599 					if(self.onprogress)
4600 						self.onprogress('Loading STL file ...', 1);
4601 					var scene = new JSC3D.Scene;
4602 					self.parseStl(scene, this.responseText);
4603 					self.onload(scene);
4604 				}
4605 			}
4606 			else {
4607 				if(JSC3D.console)
4608 					JSC3D.console.logError('Failed to load STL file "' + urlName + '".');
4609 				if(self.onerror)
4610 					self.onerror('Failed to load STL file "' + urlName + '".');
4611 			}
4612 		}
4613 	};
4614 
4615 	if(this.onprogress) {
4616 		this.onprogress('Loading STL file ...', 0);
4617 		xhr.onprogress = function(event) {
4618 			self.onprogress('Loading STL file ...', event.position / event.totalSize);
4619 		};
4620 	}
4621 
4622 	xhr.send();
4623 };
4624 
4625 /**
4626 	Set decimal precision that defines the threshold to detect and weld vertices that coincide.
4627 	@param {Number} precision the decimal preciison.
4628  */
4629 JSC3D.StlLoader.prototype.setDecimalPrecision = function(precision) {
4630 	this.decimalPrecision = precision;
4631 };
4632 
4633 /**
4634 	Parse contents of an STL file and generate the scene.
4635 	@private
4636  */
4637 JSC3D.StlLoader.prototype.parseStl = function(scene, data) {
4638 	var FACE_VERTICES           = 3;
4639 
4640 	var HEADER_BYTES            = 80;
4641 	var FACE_COUNT_BYTES        = 4;
4642 	var FACE_NORMAL_BYTES       = 12;
4643 	var VERTEX_BYTES            = 12;
4644 	var ATTRIB_BYTE_COUNT_BYTES = 2;
4645 
4646 	var mesh = new JSC3D.Mesh;
4647 	mesh.vertexBuffer = [];
4648 	mesh.indexBuffer = [];
4649 	mesh.faceNormalBuffer = [];
4650 
4651 	var isBinary = false;
4652 	var reader = new JSC3D.BinaryStream(data);
4653 
4654 	// detect whether this is an ASCII STL stream or a binary STL stream by checking a snippet of contents.
4655 	reader.skip(HEADER_BYTES + FACE_COUNT_BYTES);
4656 	for(var i=0; i<256 && !reader.eof(); i++) {
4657 		if(reader.readUInt8() > 0x7f) {
4658 			isBinary = true;
4659 			break;
4660 		}
4661 	}
4662 
4663 	if(JSC3D.console)
4664 		JSC3D.console.logInfo('This is recognised as ' + (isBinary ? 'a binary' : 'an ASCII') + ' STL file.');
4665 	
4666 	if(!isBinary) {
4667 		/*
4668 			this should be an ASCII STL file.
4669 			code contributed by Triffid Hunter.
4670 		*/
4671 
4672 		var facePattern =	'facet\\s+normal\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+' + 
4673 								'outer\\s+loop\\s+' + 
4674 									'vertex\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+' + 
4675 									'vertex\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+' + 
4676 									'vertex\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+([-+]?\\b(?:[0-9]*\\.)?[0-9]+(?:[eE][-+]?[0-9]+)?\\b)\\s+' + 
4677 								'endloop\\s+' + 
4678 							'endfacet';
4679 		var faceRegExp = new RegExp(facePattern, 'ig');
4680 		var matches = data.match(faceRegExp);
4681 
4682 		if(matches) {		
4683 			var numOfFaces = matches.length;
4684 
4685 			mesh.faceCount = numOfFaces;
4686 			var v2i = {};
4687 			
4688 			// reset regexp for vertex extraction
4689 			faceRegExp.lastIndex = 0;
4690 			faceRegExp.global = false;
4691 
4692 			// read faces
4693 			for(var r=faceRegExp.exec(data); r!=null; r=faceRegExp.exec(data)) {
4694 				mesh.faceNormalBuffer.push(parseFloat(r[1]), parseFloat(r[2]), parseFloat(r[3]));
4695 
4696 				for(var i=0; i<FACE_VERTICES; i++) {
4697 					var x = parseFloat(r[4 + (i * 3)]);
4698 					var y = parseFloat(r[5 + (i * 3)]);
4699 					var z = parseFloat(r[6 + (i * 3)]);
4700 					
4701 					// weld vertices by the given decimal precision
4702 					var vertKey = x.toFixed(this.decimalPrecision) + '-' + y.toFixed(this.decimalPrecision) + '-' + z.toFixed(this.decimalPrecision);
4703 					var vi = v2i[vertKey];
4704 					if(vi === undefined) {
4705 						vi = mesh.vertexBuffer.length / 3;
4706 						v2i[vertKey] = vi;
4707 						mesh.vertexBuffer.push(x);
4708 						mesh.vertexBuffer.push(y);
4709 						mesh.vertexBuffer.push(z);
4710 					}
4711 					mesh.indexBuffer.push(vi);
4712 				}
4713 				
4714 				// mark the end of the indices of a face
4715 				mesh.indexBuffer.push(-1);
4716 			}
4717 		}
4718 	}
4719 	else {
4720 		/*
4721 			this is a binary STL file
4722 		*/
4723 
4724 		reader.reset();
4725 	
4726 		// skip 80-byte's STL file header
4727 		reader.skip(HEADER_BYTES);
4728 	
4729 		// read face count
4730 		var numOfFaces = reader.readUInt32();
4731 	
4732 		// calculate the expected length of the stream
4733 		var expectedLen = HEADER_BYTES + FACE_COUNT_BYTES + 
4734 							(FACE_NORMAL_BYTES + VERTEX_BYTES * FACE_VERTICES + ATTRIB_BYTE_COUNT_BYTES) * numOfFaces;
4735 		
4736 		// file is not complete
4737 		if(reader.size() < expectedLen) {
4738 			if(JSC3D.console)
4739 				JSC3D.console.logError('Failed to parse contents of the file. It seems not complete.');
4740 			return;
4741 		}
4742 	
4743 		mesh.faceCount = numOfFaces;
4744 		var v2i = {};
4745 	
4746 		// read faces
4747 		for(var i=0; i<numOfFaces; i++) {
4748 			// read normal vector of a face
4749 			mesh.faceNormalBuffer.push(reader.readFloat32());
4750 			mesh.faceNormalBuffer.push(reader.readFloat32());
4751 			mesh.faceNormalBuffer.push(reader.readFloat32());
4752 	
4753 			// read all 3 vertices of a face
4754 			for(var j=0; j<FACE_VERTICES; j++) {
4755 				// read coords of a vertex
4756 				var x, y, z;
4757 				x = reader.readFloat32();
4758 				y = reader.readFloat32();
4759 				z = reader.readFloat32();
4760 	
4761 				// weld vertices by the given decimal precision
4762 				var vertKey = x.toFixed(this.decimalPrecision) + '-' + y.toFixed(this.decimalPrecision) + '-' + z.toFixed(this.decimalPrecision);
4763 				var vi = v2i[vertKey];
4764 				if(vi != undefined) {
4765 					mesh.indexBuffer.push(vi);
4766 				}
4767 				else {
4768 					vi = mesh.vertexBuffer.length / 3;
4769 					v2i[vertKey] = vi;
4770 					mesh.vertexBuffer.push(x);
4771 					mesh.vertexBuffer.push(y);
4772 					mesh.vertexBuffer.push(z);
4773 					mesh.indexBuffer.push(vi);
4774 				}
4775 			}
4776 	
4777 			// mark the end of the indices of a face
4778 			mesh.indexBuffer.push(-1);
4779 	
4780 			// skip 2-bytes' 'attribute byte count' field, since we do not deal with any additional attribs
4781 			reader.skip(ATTRIB_BYTE_COUNT_BYTES);
4782 		}
4783 	}
4784 	
4785 	// add mesh to scene
4786 	if(!mesh.isTrivial())
4787 		scene.addChild(mesh);
4788 };
4789 
4790 JSC3D.StlLoader.prototype.onload = null;
4791 JSC3D.StlLoader.prototype.onerror = null;
4792 JSC3D.StlLoader.prototype.onprogress = null;
4793 JSC3D.StlLoader.prototype.onresource = null;
4794 JSC3D.StlLoader.prototype.decimalPrecision = 3;
4795 
4796 JSC3D.LoaderSelector.registerLoader('stl', JSC3D.StlLoader);
4797