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