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