本篇主要通过分析Tony Parisi的sim.js库(原版代码托管于:https://github.com/tparisi/WebGLBook/tree/master/sim),总结基础Web3D框架的编写方法。在上一篇的基础上,要求读者具有简短英文阅读或者查字典的能力。
限于水平和时间,本文难免出现错误与遗漏,您在阅读过程中如果遇到错误或者疑问请在评论区中指出,我将尽快回复。
为提高JavaScript编程效率,建议使用WebStorm工具进行网页程序编写,WebStorm官网:。
上一篇中,我们把程序的所有文件放在同一个目录下,这种文件组织方式适用于简单的功能测试,但当文件数量更多时则会变得混乱不堪,我们在编写一般规模的Web3D程序时可参考下图进行文件组织:
该组织方式把JavaScript文件分为LIB和PAGE两部分,LIB保存一般不做修改的库文件,PAGE保存为特定页面编写的js文件,如果页面js较多可在PAGE中再分离出子文件夹。
MODEL下的每个文件夹都是一个JSON类型的模型,可以看到其中有保存纹理信息的jpg文件和保存顶点数组、法线向量、纹理坐标的文本文件。
上一篇的代码中,我们把所有需要多次调用的对象设为了全局变量和全局函数,当代码量增多时这种“全局管理”方式将面临巨大的挑战,随然我们可以用规范的变量命名或者变量数组来尽可能避免变量名重复,但全局管理方式仍缺少对变量间关系的描述方法,这时使用“面向对象”的变量管理方法似乎是唯一的选择。
下面进入正题:
Sim = {};Sim.Publisher = function() { 9 this.messageTypes = {}; 10 } 11 12 Sim.Publisher.prototype.subscribe = function(message, subscriber, callback) { 13 var subscribers = this.messageTypes[message]; 14 if (subscribers) 15 { 16 if (this.findSubscriber(subscribers, subscriber) != -1) 17 { 18 return; 19 } 20 } { 23 subscribers = []; 24 this.messageTypes[message] = subscribers; 25 } 26 27 subscribers.push({ subscriber : subscriber, callback : callback }); 28 } 29 30 Sim.Publisher.prototype.unsubscribe = function(message, subscriber, callback) { 31 if (subscriber) 32 { 33 var subscribers = this.messageTypes[message]; (subscribers) 36 { 37 var i = this.findSubscriber(subscribers, subscriber, callback); 38 if (i != -1) 39 { 40 this.messageTypes[message].splice(i, 1); 41 } 42 } 43 } { .messageTypes[message]; 47 } 48 } 49 50 Sim.Publisher.prototype.publish = function(message) { 51 var subscribers = this.messageTypes[message]; (subscribers) 54 { 55 for (var i = 0; i < subscribers.length; i++) 56 { 57 var args = []; 58 for (var j = 0; j < arguments.length - 1; j++) 59 { 60 args.push(arguments[j + 1]); 61 } 62 subscribers[i].callback.apply(subscribers[i].subscriber, args); 63 } 64 } 65 } 66 67 Sim.Publisher.prototype.findSubscriber = function (subscribers, subscriber) { 68 for (var i = 0; i < subscribers.length; i++) 69 { 70 if (subscribers[i] == subscriber) 71 { 72 return i; 73 } 74 } -1; 77 } Sim.App = function() 82 { 83 Sim.Publisher.call(this); .renderer = null; 87 this.scene = null; 88 this.camera = null; 89 this.objects = []; } 92 93 Sim.App.prototype = new Sim.Publisher; Sim.App.prototype.init = { 98 param = param || {}; 99 var container = param.container; 100 var canvas = param.canvas; webglAvailable() { 107 try{ 108 var canvas=document.createElement("canvas"); 109 return !!(window.WebGLRenderingContext 110 &&(canvas.getContext("webgl")||canvas.getContext("experimental-webgl")) 111 ); 112 }catch(e){ ; 114 } 115 } 116 if(webglAvailable()){ 117 var renderer=new THREE.WebGLRenderer({ antialias: true, canvas: canvas }); 118 }else{ } renderer.setClearColor( 0xffffff ); renderer.setSize(container.offsetWidth, container.offsetHeight); 126 container.appendChild( renderer.domElement ); 127 container.onfocus=function(){ } scene = new THREE.Scene(); 134 scene.add( new THREE.AmbientLight( 0x505050 ) ); 135 scene.data = this; camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 1, 10000 ); 139 camera.position.set( 0, 0, 3.3333 ); 140 141 scene.add(camera); root = new THREE.Object3D(); 146 scene.add(root); projector = new THREE.Projector(); .container = container; 155 this.renderer = renderer; 156 this.scene = scene; 157 this.camera = camera; 158 this.projector = projector; 159 this.root = root; .initMouse(); 164 this.initKeyboard(); 165 this.addDomHandlers(); 166 } Sim.App.prototype.run = function() 171 { 172 this.update(); 173 this.renderer.render( this.scene, this.camera ); requestAnimFrame(} Sim.App.prototype.update = function() 182 { 183 var i, len; 184 len = this.objects.length; 185 for (i = 0; i < len; i++) .objects[i].update(); 188 } 189 } Sim.App.prototype.addObject = function(obj) 195 { (obj.object3D) 201 { 202 this.root.add(obj.object3D); 203 } 204 } Sim.App.prototype.removeObject = function(obj) 207 { 208 var index = this.objects.indexOf(obj); 209 if (index != -1) 210 { 211 this.objects.splice(index, 1); (obj.object3D) 215 { 216 this.root.remove(obj.object3D); 217 } 218 } 219 } Sim.App.prototype.initMouse = function() 225 { that = this; 230 dom.addEventListener( 'mousemove', 231 function(e) { that.onDocumentMouseMove(e); }, false ); 232 dom.addEventListener( 'mousedown', 233 function(e) { that.onDocumentMouseDown(e); }, false ); 234 dom.addEventListener( 'mouseup', 235 function(e) { that.onDocumentMouseUp(e); }, false ); $(dom).mousewheel( 239 function(e, delta) { 240 that.onDocumentMouseScroll(e, delta); 241 } 242 ); .overObject = null; .clickedObject = null; 248 } Sim.App.prototype.initKeyboard = function() 251 { 252 var dom = this.renderer.domElement; that = this; 255 dom.addEventListener( 'keydown', 256 function(e) { that.onKeyDown(e); }, false ); 257 dom.addEventListener( 'keyup', 258 function(e) { that.onKeyUp(e); }, false ); 259 dom.addEventListener( 'keypress', 260 function(e) { that.onKeyPress(e); }, false ); dom.setAttribute("tabindex", 1); 265 dom.style.outline='none'; 266 dom.focus(); 267 } 268 269 Sim.App.prototype.addDomHandlers = function() 270 { 271 var that = this; window.addEventListener( 'resize', function(event) { that.onWindowResize(event); }, false ); 274 } Sim.App.prototype.onDocumentMouseMove = function(event) 278 { (this.clickedObject && this.clickedObject.handleMouseMove) hitpoint = intersected = this.objectFromMouse(event.pageX, event.pageY); (intersected.object == this.clickedObject) hitpoint = intersected.point; 289 hitnormal = intersected.normal; 290 } 291 this.clickedObject.handleMouseMove(event.pageX, event.pageY, hitpoint, hitnormal); } {handled = false; oldObj = intersected = this.objectFromMouse(event.pageX, event.pageY); ( { 304 if (oldObj) .container.style.cursor = 'auto'; (oldObj.handleMouseOut) 309 { 310 oldObj.handleMouseOut(event.pageX, event.pageY); 311 } 312 } (this.overObject) 315 { 316 if (this.overObject.overCursor) 317 { } (this.overObject.handleMouseOver) 322 { 323 this.overObject.handleMouseOver(event.pageX, event.pageY); 324 } 325 } } (!handled && this.handleMouseMove) 331 { 332 this.handleMouseMove(event.pageX, event.pageY); } 335 } 336 } Sim.App.prototype.onDocumentMouseDown = function(event) 339 { 340 event.preventDefault(); handled = false; intersected = this.objectFromMouse(event.pageX, event.pageY); 345 if (intersected.object) 346 { 347 if (intersected.object.handleMouseDown) 348 { 349 intersected.object.handleMouseDown(event.pageX, event.pageY, intersected.point, intersected.normal); 350 this.clickedObject = intersected.object; 351 handled = true; 352 } 353 } (!handled && this.handleMouseDown) 356 { 357 this.handleMouseDown(event.pageX, event.pageY); 358 } 359 } 360 361 Sim.App.prototype.onDocumentMouseUp = function(event) 362 { 363 event.preventDefault(); handled = false; intersected = this.objectFromMouse(event.pageX, event.pageY); 368 if (intersected.object) 369 { 370 if (intersected.object.handleMouseUp) 371 { 372 intersected.object.handleMouseUp(event.pageX, event.pageY, intersected.point, intersected.normal); 373 handled = true; 374 } 375 } (!handled && this.handleMouseUp) 378 { 379 this.handleMouseUp(event.pageX, event.pageY); 380 } .clickedObject = null; 383 } 384 385 Sim.App.prototype.onDocumentMouseScroll = function(event, delta) 386 { 387 event.preventDefault(); (this.handleMouseScroll) 390 { 391 this.handleMouseScroll(delta); 392 } 393 } 394 395 Sim.App.prototype.objectFromMouse = function(pagex, pagey) 396 { offset = $(this.renderer.domElement).offset(); 400 var eltx = pagex - offset.left; 401 var elty = pagey - offset.top; vpx = ( eltx / this.container.offsetWidth ) * 2 - 1; 406 var vpy = - ( elty / this.container.offsetHeight ) * 2 + 1; vector = vector.unproject(raycaster = new THREE.Raycaster(this.camera.position,vector.subVectors(vector,this.camera.position).normalize()); intersects = raycaster.intersectObjects(this.scene.children,true); ( intersects.length > 0 ) { var i = 0; 426 while(!intersects[i].object.visible) 427 { 428 i++; 429 } 430 431 var intersected = intersects[i]; 432 var mat = new THREE.Matrix4().getInverse(intersected.object.matrixWorld); 433 var point = mat.multiplyVector3(intersected.point); (var i=0;i<intersects.length;i++) 438 { 439 if(intersects[i].object.visible&&intersects[i].face) intersected = intersects[i]; 442 var mat = new THREE.Matrix4().getInverse(intersected.object.matrixWorld); (this.findObjectFromIntersected(intersected.object, intersected.point, intersected.face.normal)); 445 } 446 } } { 451 return { object : null, point : null, normal : null }; 452 } 453 } 454 455 Sim.App.prototype.findObjectFromIntersected = function(object, point, normal) (object.data) 459 { 460 return { object: object.data, point: point, normal: normal }; 461 } (object.parent) 463 { .findObjectFromIntersected(object.parent, point, normal); 465 } { 468 return { object : null, point : null, normal : null }; 469 } 470 } Sim.App.prototype.onKeyDown = function(event) 474 { event.preventDefault(); (this.handleKeyDown) 480 { 481 this.handleKeyDown(event.keyCode, event.charCode); 482 } 483 } 484 485 Sim.App.prototype.onKeyUp = function(event) 486 { event.preventDefault(); (this.handleKeyUp) 491 { 492 this.handleKeyUp(event.keyCode, event.charCode); 493 } 494 } 495 496 Sim.App.prototype.onKeyPress = function(event) 497 { event.preventDefault(); (this.handleKeyPress) 502 { 503 this.handleKeyPress(event.keyCode, event.charCode); 504 } 505 } Sim.App.prototype.onWindowResize = function(event) { .renderer.setSize(this.container.offsetWidth, this.container.offsetHeight); .camera.aspect = this.container.offsetWidth / this.container.offsetHeight;//宽高比 513 this.camera.updateProjectionMatrix();//投影矩阵 514 515 } Sim.App.prototype.focus = function() 518 { 519 if (this.renderer && this.renderer.domElement) 520 { 521 this.renderer.domElement.focus(); 522 } 523 } Sim.Object = function() 529 { 530 Sim.Publisher.call(this); .object3D = null; 533 this.children = []; 534 } 535 536 Sim.Object.prototype = new Sim.Publisher; { 540 } 541 542 Sim.Object.prototype.update = function() 543 { } Sim.Object.prototype.setPosition = function(x, y, z) 550 { 551 if (this.object3D) 552 { 553 this.object3D.position.set(x, y, z); 554 } 555 } Sim.Object.prototype.setScale = function(x, y, z) 560 { 561 if (this.object3D) 562 { 563 this.object3D.scale.set(x, y, z); 564 } 565 } Sim.Object.prototype.setVisible = function(visible) 570 { 571 function setVisible(obj, visible) 572 { 573 obj.visible = visible; 574 var i, len = obj.children.length; 575 for (i = 0; i < len; i++) 576 { 577 setVisible(obj.children[i], visible); 578 } 579 } (this.object3D) 582 { 583 setVisible(this.object3D, visible); 584 } 585 } Sim.Object.prototype.updateChildren = function() 589 { 590 var i, len; 591 len = this.children.length; 592 for (i = 0; i < len; i++) 593 { 594 this.children[i].update(); 595 } 596 } 597 598 Sim.Object.prototype.setObject3D = function(object3D) 599 { 600 object3D.data = this;//建立双向链表,可以相互调用 } Sim.Object.prototype.addChild = function(child) 607 { (child.object3D) { 613 this.object3D.add(child.object3D); 614 } 615 } 616 617 Sim.Object.prototype.removeChild = function(child) 618 { 619 var index = this.children.indexOf(child); 620 if (index != -1) 621 { 622 this.children.splice(index, 1); (child.object3D) 625 { 626 this.object3D.remove(child.object3D); 627 } 628 } 629 } Sim.Object.prototype.getScene = function() 634 { 635 var scene = null; 636 if (this.object3D) 637 { 638 var obj = this.object3D; 639 while (obj.parent) 640 { 641 obj = obj.parent; 642 } 643 644 scene = obj; 645 } scene; 648 } 649 650 Sim.Object.prototype.getApp = function() 651 { 652 var scene = this.getScene(); } key codes 659 37: left 660 38: up 661 39: right 662 40: down Sim.KeyCodes = {}; 665 Sim.KeyCodes.KEY_LEFT = 37; 666 Sim.KeyCodes.KEY_UP = 38; 667 Sim.KeyCodes.KEY_RIGHT = 39; 668 Sim.KeyCodes.KEY_DOWN = 40;