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