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 相关文章推荐
jQuery UI的Dialog无法提交问题的解决方法
Jan 11 Javascript
javascript 快速排序函数代码
May 30 Javascript
iframe异步加载实现点击左边菜单加载右边内容实例讲解
Mar 04 Javascript
js 字符串转换成数字的三种方法
Mar 23 Javascript
js键盘上下左右键怎么触发function(实例讲解)
Dec 14 Javascript
jquery解析xml字符串示例分享
Mar 25 Javascript
网页下载文件期间如何防止用户对网页进行其他操作
Jun 27 Javascript
String字符串截取的四种方式总结
Nov 28 Javascript
SVG描边动画
Feb 23 Javascript
Angular CLI在Angular项目中如何使用scss详解
Apr 10 Javascript
p5.js临摹动态图形实现方法详解
Oct 23 Javascript
在vue中使用Base64转码的案例
Aug 07 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
全局记录程序片段的运行时间 正确找到程序逻辑耗时多的断点
2011/01/06 PHP
PHP+SQL 注入攻击的技术实现以及预防办法
2011/01/27 PHP
php简单解析mysqli查询结果的方法(2种方法)
2016/06/29 PHP
PHP导出带样式的Excel示例代码
2016/08/28 PHP
Yii框架结合sphinx,Ajax实现搜索分页功能示例
2016/10/18 PHP
一些有关检查数据的JS代码
2006/09/07 Javascript
javascript实现仿银行密码输入框效果的代码
2007/12/13 Javascript
javascript让setInteval里的函数参数中的this指向特定的对象
2010/01/31 Javascript
JQuery中clone方法复制节点
2015/05/18 Javascript
JavaScript简单修改窗口大小的方法
2015/08/03 Javascript
Jquery easyui 实现动态树
2015/11/17 Javascript
javascript拖拽效果延伸学习
2016/04/04 Javascript
AngularJS使用指令增强标准表单元素功能
2016/07/01 Javascript
AngularJs Modules详解及示例代码
2016/09/01 Javascript
jQuery中on方法使用注意事项详解
2017/02/15 Javascript
[01:14]辉夜杯战队访谈宣传片—NEWBEE.Y
2015/12/26 DOTA
Python随机生成数模块random使用实例
2015/04/13 Python
python中kmeans聚类实现代码
2018/02/23 Python
python实战之实现excel读取、统计、写入的示例讲解
2018/05/02 Python
使用python进行广告点击率的预测的实现
2019/07/04 Python
Python调用C语言的实现
2019/07/26 Python
django框架ModelForm组件用法详解
2019/12/11 Python
Python中zip函数如何使用
2020/06/04 Python
Melijoe时尚童装德国官网:Melijoe德国
2016/09/03 全球购物
100%植物性、有机、即食餐:Sakara Life
2018/10/25 全球购物
L’urv官网:精品女性运动服品牌
2019/07/07 全球购物
《蚕姑娘》教学反思
2014/04/15 职场文书
优秀共产党员推荐材料
2014/12/18 职场文书
基层党支部承诺书
2015/04/30 职场文书
2015年信息宣传工作总结
2015/05/26 职场文书
门球健将观后感
2015/06/16 职场文书
2016春季小学开学寄语
2015/12/03 职场文书
分家协议书范本
2016/03/22 职场文书
PHP 技巧 * SVG 保存为图片(分享图生成)
2021/04/02 PHP
Python 数据可视化之Seaborn详解
2021/11/02 Python
Nginx的gzip相关介绍
2022/05/11 Servers