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