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