Three.js源码阅读笔记(Object3D类)


Posted in Javascript onDecember 27, 2012

这是Three.js源码阅读笔记的第二篇,直接开始。
Core::Object3D
Object3D似乎是Three.js框架中最重要的类,相当一部分其他的类都是继承自Object3D类,比如场景类、几何形体类、相机类、光照类等等:他们都是3D空间中的对象,所以称为Object3D类。Object3D构造函数如下:

THREE.Object3D = function () { 
THREE.Object3DLibrary.push( this ); 
this.id = THREE.Object3DIdCount ++; 
this.name = ''; 
this.properties = {}; 
this.parent = undefined; 
this.children = []; 
this.up = new THREE.Vector3( 0, 1, 0 ); 
this.position = new THREE.Vector3(); 
this.rotation = new THREE.Vector3(); 
this.eulerOrder = THREE.Object3D.defaultEulerOrder; 
this.scale = new THREE.Vector3( 1, 1, 1 ); 
this.renderDepth = null; 
this.rotationAutoUpdate = true; 
this.matrix = new THREE.Matrix4(); 
this.matrixWorld = new THREE.Matrix4(); 
this.matrixRotationWorld = new THREE.Matrix4(); 
this.matrixAutoUpdate = true; 
this.matrixWorldNeedsUpdate = true; 
this.quaternion = new THREE.Quaternion(); 
this.useQuaternion = false; 
this.boundRadius = 0.0; 
this.boundRadiusScale = 1.0; 
this.visible = true; 
this.castShadow = false; 
this.receiveShadow = false; 
this.frustumCulled = true; 
this._vector = new THREE.Vector3(); 
};

在介绍函数之前,需要先介绍一下这个类的几个重要属性。
属性parent和children说明,通常需要使用树来管理众多Object3D对象。比如一辆行驶的汽车是一个Object3D对象,控制汽车行驶路线的逻辑在该对象内部实现,汽车的每个顶点经过模型矩阵的处理后,都位于正确的位置;但是汽车摆动的雨刮器,其不但随着汽车行驶方向运动,而且自身相对汽车也在左右摆动,这个摆动的逻辑无法在汽车这个对象内部的实现。解决的方法是,将雨刮器设定为汽车的chidren,雨刮器内部的逻辑只负责其相对于汽车的摆动。在这种树状结构下,一个场景Scene实际上就是最顶端的Object3D,它的模型矩阵就是视图矩阵(取决于相机)的逆矩阵。

属性matrix和matrixWorld就很好理解了,matrix表示本地的模型矩阵,仅仅表示该对象的运动,而matrixWorld则需要依次向父亲节点迭代,每一次迭代都左乘父亲对象的本地模型矩阵,直到Scene对象——当然,实际上是左乘父亲对象的全局模型矩阵。

属性position、rotation、scale表示模型矩阵的三种变换部分,在Matrix4类中有相关说明。rotation和eulerOrder共同描述了一个旋转状态,quaternion也可以描述一个旋转状态,具体使用哪种方法要看useQuation的布尔值。

可以看到,关于该Object3D对象最重要的“变换状态”信息实际上是存储在两个“备份”中的,一个是matrix对象,还有一个是position等属性,两部分应当保持一致,如果通过某种方法改变了一个备份,则另一个备份也应该在适当的时候更新。还有一些其他属性从字面和类型上就能看出其含义,不再单独列出了。下面说函数:
函数applyMatrix(matrix)将参数matrix左乘到this.matrix上,实际上就是对该Object3D对象实行某个变换(该变换可能要经过好几步基本变换,但是已经存储在参数matrix里面了)。注意,在对this.matrix执行完左乘之后,;立刻更新了position等参数的值。比起下面几个变换函数,该函数更“高级”,允许开发者自由指定变换矩阵,而不是说“朝着x轴前进5单位距离”。

applyMatrix: function ( matrix ) { 
this.matrix.multiply( matrix, this.matrix ); 
this.scale.getScaleFromMatrix( this.matrix ); 
var mat = new THREE.Matrix4().extractRotation( this.matrix ); 
this.rotation.setEulerFromRotationMatrix( mat, this.eulerOrder ); 
this.position.getPositionFromMatrix( this.matrix ); 
},

函数translate(distance, axis)令该对象向axis轴指定的方向前进distance距离。函数translateX(distance),translateY(distance),translateZ(distance)令其向X,Y,Z轴前进distance距离。注意这些函数仅仅改变了position对象的值,而不曾改变matrix的值。
translate: function ( distance, axis ) { 
this.matrix.rotateAxis( axis ); 
this.position.addSelf( axis.multiplyScalar( distance ) ); 
}, 
translateX: function ( distance ) { 
this.translate( distance, this._vector.set( 1, 0, 0 ) ); 
},

函数localToWorld(vector)将本地坐标转化为世界坐标中,函数worldToLocal则正好相反。注意这里的vector本地坐标指的是未变换之前的坐标,也就是说雨刮器的默认位置的顶点坐标。

函数lookAt(eye,center,up)执行其matrix属性对象的lookAt函数(之前介绍过,matrix4对象也有一个lookAt函数),一般用于相机对象。该函数仅仅改变了旋转状态,所以当matrix属性对象执行完之后,如果属性rotationAutoUpdate为真,则会更新rotation或quaternion的值,更新哪一个取决于属性useQuation。
函数add(object)和函数remove(object)从当前Object3D对象中添加一个子对象,或删除一个子对象,了解到场景中的众多Object3D对象是用树来管理的,这就很容易理解了。

函数traverse(callback)遍历调用者和调用者的所有后代,callback参数是一个函数,被调用者和每一个后代对象调用callback(this)。

traverse: function ( callback ) { 
callback( this ); 
for ( var i = 0, l = this.children.length; i < l; i ++ ) { 
this.children[ i ].traverse( callback ); 
} 
},

函数getChildByName(name,recursive)通过字符串在调用者的子元素(recursive为false)或后代元素(recursive为true)中查询属性name符合的对象返回。

函数getDescendants(array)将调用者的所有后代对象全部push到数组array中。
函数updateMatrix()和updateMatrixWorld(force)将根据position,rotation或quaternion,scale参数更新matrix和matrixWorld。updateMatrixWorld还会更新所有后代元素的matrixWorld,如果force值为真或者调用者本身的matrixWorldNeedsUpdate值为真。在函数applyMatrix(matrix)中,改变了matrix值后立刻就更新了position,rotation等属性,但在函数translate(distance,axis)中改变了position等变量(或者直接改变position等属性)后并没有立刻更新matrix值,这时应该手动调用updateMatrix()。这些细节值得注意,你也许会认为应该加入事件监听,一旦一个值发生变化,其他所有的都会立刻更新,但我想在,可能是出于这方面的考虑:适当的时候更新会带来更高的效率——比如可能会频繁地改变rotation值,但是仅仅在使用matrix属性之前,才对其进行更新。

updateMatrix: function () { 
this.matrix.setPosition( this.position ); 
if ( this.useQuaternion === false ) { 
this.matrix.setRotationFromEuler( this.rotation, this.eulerOrder ); 
} else { 
this.matrix.setRotationFromQuaternion( this.quaternion ); 
} 
if ( this.scale.x !== 1 || this.scale.y !== 1 || this.scale.z !== 1 ) { 
this.matrix.scale( this.scale ); 
this.boundRadiusScale = Math.max( this.scale.x, Math.max( this.scale.y, this.scale.z ) ); 
} 
this.matrixWorldNeedsUpdate = true; 
}, 
updateMatrixWorld: function ( force ) { 
if ( this.matrixAutoUpdate === true ) this.updateMatrix(); 
if ( this.matrixWorldNeedsUpdate === true || force === true ) { 
if ( this.parent === undefined ) { 
this.matrixWorld.copy( this.matrix ); 
} else { 
this.matrixWorld.multiply( this.parent.matrixWorld, this.matrix ); 
} 
this.matrixWorldNeedsUpdate = false; 
force = true; 
} 
for ( var i = 0, l = this.children.length; i < l; i ++ ) { 
this.children[ i ].updateMatrixWorld( force ); 
} 
},

函数deallocate手动将调用者占用的空间释放掉,当不再需要该对象时这样做。
Core::Projectors
管理投影矩阵的类,代码太复杂了,我猜会涉及到render类里的操作,等到适当的时候再看吧。
Core::UV
该构造函数产生一个材质坐标类——就是材质上的坐标,往往与顶点对应起来,光栅化后每个像素都有一个材质坐标,再从材质上“取色”以实现纹理。
THREE.UV = function ( u, v ) { 
this.u = u || 0; 
this.v = v || 0; 
};

材质坐标类就是一个简化的vector2类,除了属性名称不同而已。
Core::Ray Core::Rectangle Core:Spline
射线类,有原点、方向、远近截断点。在点光源中应该有应用。矩形类、曲线类,相对都比较简单,也不那么“核心”,以后再看吧。
Core::Geometry
Geometry类也是非常重要的一类,表示一个由顶点和表面构成的几何形体。
THREE.Geometry = function () { 
THREE.GeometryLibrary.push( this ); 
this.id = THREE.GeometryIdCount ++; 
this.name = ''; 
this.vertices = []; 
this.colors = []; 
this.normals = []; 
this.faces = []; 
this.faceUvs = [[]]; 
this.faceVertexUvs = [[]]; 
this.morphTargets = []; 
this.morphColors = []; 
this.morphNormals = []; 
this.skinWeights = []; 
this.skinIndices = []; 
this.lineDistances = []; 
this.boundingBox = null; 
this.boundingSphere = null; 
this.hasTangents = false; 
this.dynamic = true; 
this.verticesNeedUpdate = false; 
this.elementsNeedUpdate = false; 
this.uvsNeedUpdate = false; 
this.normalsNeedUpdate = false; 
this.tangentsNeedUpdate = false; 
this.colorsNeedUpdate = false; 
this.lineDistancesNeedUpdate = false; 
this.buffersNeedUpdate = false; 
};

以下两组属性最重要
属性vertics是一个数组,每个元素是vector3类型的对象,表示一个顶点坐标。属性colors和normals表示和顶点对应的颜色值和发现向量,只有在很少的情况下才使用,大部分情况下,顶点的颜色和发现时在“表面”中定义的——如果立方体的6面颜色各不相同,则每个顶点实在不同的面上是不同的颜色。

属性faces是一个数组,每个元素是face4或face3类型的对象,之前介绍face3的时候说到,face中存储的仅仅是顶点的索引值,通过索引值就可以在数组vertices中取到顶点的坐标值。

下面说函数
applyMatrix(matrix)函数更新geometry中的所有顶点坐标和表面的法线向量,所做的实际上是用变换矩阵matrix对geometry形体进行空间变换。normalMatrix是参数matrix左上角3×3矩阵的逆转置矩阵,该矩阵用来旋转矢量(法线,而不是顶点坐标)。

applyMatrix: function ( matrix ) { 
var normalMatrix = new THREE.Matrix3(); 
normalMatrix.getInverse( matrix ).transpose(); 
for ( var i = 0, il = this.vertices.length; i < il; i ++ ) { 
var vertex = this.vertices[ i ]; 
matrix.multiplyVector3( vertex ); 
} 
for ( var i = 0, il = this.faces.length; i < il; i ++ ) { 
var face = this.faces[ i ]; 
normalMatrix.multiplyVector3( face.normal ).normalize(); 
for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { 
normalMatrix.multiplyVector3( face.vertexNormals[ j ] ).normalize(); 
} 
matrix.multiplyVector3( face.centroid ); 
} 
},

函数ComputeCentroid()计算几何形体中每个表面的重心(不是几何形体自己的重心)。这个函数似乎应当放到face类的原型上会更好,但是由于face类内部无法获取点的坐标(除非再将点坐标数组的引用作为参数传入构造函数,这样代价就大了)而仅仅是索引值,所以只好在geometry类的原型上定义了。下面几个函数都是类似的情况(事实上,face类几乎没有什么成员函数)。

函数computeFaceNormals()和computeVertexNormals(areaWeight)计算法线向量,前者影响的是face数组中每个元素的normal属性,一个face只有1个;后者face数组中每个元素的vertexNormal属性,一个face3型对象有3个,一个face4型对象有4个,但是需要注意的是,被多个表面共享的顶点,其法线向量只有一个,同时受到多个表面的影响。比如中心在原点,三组表面都垂直于轴的立方体,其第一象限中的顶点,法线向量是(1,1,1)的归一化。虽然看上去不可思议,平面的顶点的法线居然不是垂直于平面的,但这种指定法线的方法在利用平面模拟曲面的时候有很好的效果。

函数createMorphNormal为每一个morph创建法线。morph应该是用作显示固定连续动画的变形效果。
函数mergeVertics将坐标值相同的点剔除,同时更新face对象中的点索引值。
Core::Quaternian
四维数旋转类用另一种方式表达一个旋转变换,相比用rotation,可以避免万向节死锁问题。

THREE.Quaternion = function( x, y, z, w ) { 
this.x = x || 0; 
this.y = y || 0; 
this.z = z || 0; 
this.w = ( w !== undefined ) ? w : 1; 
};

如果不谈函数,Quaternian就是一个简单的vector4类型对象。
函数setFromEuler(v,order)通过一次欧拉旋转设置四维数旋转。
函数setFromAxis(axis,angle)通过绕任意轴旋转设定四维数旋转。
函数setFromRotationMatrix(matrix)通过旋转矩阵设置四维数旋转。
还有一些和vector4类相同的函数这里就不列了。
Javascript 相关文章推荐
JS获取父节点方法
Aug 20 Javascript
(function($){...})(jQuery)的意思
Jul 22 Javascript
JavaScript高级程序设计 读书笔记之十 本地对象Date日期
Feb 27 Javascript
jQuery图片的展开和收缩实现代码
Apr 16 Javascript
解析Jquery中如何把一段html代码动态写入到DIV中(实例说明)
Jul 09 Javascript
js计算任意值之间随机数的方法
Jan 16 Javascript
jQuery UI库中dialog对话框功能使用全解析
Apr 23 Javascript
js阻止默认浏览器行为与冒泡行为的实现代码
May 15 Javascript
JavaScript中数组的22种方法必学(推荐)
Jul 20 Javascript
微信小程序 出现47001 data format error原因解决办法
Mar 10 Javascript
JavaScript中使用Spread运算符的八种方法总结
Jun 18 Javascript
基于Vue全局组件与局部组件的区别说明
Aug 11 Javascript
Three.js源码阅读笔记(物体是如何组织的)
Dec 27 #Javascript
Three.js源码阅读笔记(光照部分)
Dec 27 #Javascript
通过jQuery源码学习javascript(三)
Dec 27 #Javascript
JS原型对象通俗&quot;唱法&quot;
Dec 27 #Javascript
通过jQuery源码学习javascript(二)
Dec 27 #Javascript
js 判断一个元素是否在页面中存在
Dec 27 #Javascript
通过jQuery源码学习javascript(一)
Dec 27 #Javascript
You might like
实现树状结构的两种方法
2006/10/09 PHP
探讨PHP中this,self,parent的区别详解
2013/06/08 PHP
php 问卷调查结果统计
2015/10/08 PHP
PHP中addslashes()和stripslashes()实现字符串转义和还原用法实例
2016/01/07 PHP
php从数据库中获取数据用ajax传送到前台的方法
2018/08/20 PHP
PHP将英文数字转换为阿拉伯数字实例讲解
2019/01/28 PHP
JavaScript 学习笔记(十五)
2010/01/28 Javascript
将Datatable转化成json发送前台实现思路
2013/09/06 Javascript
css配合jquery美化 select
2013/11/29 Javascript
JQuery中serialize()、serializeArray()和param()方法示例介绍
2014/07/31 Javascript
js实现文本框宽度自适应文本宽度的方法
2015/08/13 Javascript
js style.display=block显示布局错乱问题的解决方法
2016/09/21 Javascript
关于微信jssdk实现多图片上传的一点心得分享
2016/12/13 Javascript
Vue2.0使用过程常见的一些问题总结学习
2017/04/10 Javascript
集合Bootstrap自定义confirm提示效果
2017/09/19 Javascript
jQuery中图片展示插件highslide.js的简单dom
2018/04/22 jQuery
不得不知的ES6小技巧
2018/07/28 Javascript
微信小程序开发的基本流程步骤
2019/01/31 Javascript
详解vue项目中实现图片裁剪功能
2019/06/07 Javascript
vue轮播组件实现$children和$parent 附带好用的gif录制工具
2019/09/26 Javascript
使用 Angular RouteReuseStrategy 缓存(路由)组件的实例代码
2019/11/01 Javascript
JS运算符简单用法示例
2020/01/19 Javascript
Python RuntimeError: thread.__init__() not called解决方法
2015/04/28 Python
浅谈Scrapy网络爬虫框架的工作原理和数据采集
2019/02/07 Python
django在保存图像的同时压缩图像示例代码详解
2020/02/11 Python
三年级数学教学反思
2014/01/31 职场文书
《风筝》教学反思
2014/04/10 职场文书
校园文明标语
2014/06/13 职场文书
春游踏青活动方案
2014/08/14 职场文书
大学生感恩父母演讲稿
2014/08/28 职场文书
销售员试用期自我评价
2014/09/15 职场文书
酒店圣诞节活动总结
2015/05/06 职场文书
黄河绝恋观后感
2015/06/08 职场文书
经营场所使用证明
2015/06/19 职场文书
手把手教你制定暑期学习计划,让你度过充实的暑假
2019/08/22 职场文书
mysql数据库入门第一步之创建表
2021/05/14 MySQL