1. 程式人生 > >three.js 原始碼註釋(二十七)Core/BufferGeometry.js

three.js 原始碼註釋(二十七)Core/BufferGeometry.js


俺也是剛開始學,好多地兒肯定不對還請見諒.

以下程式碼是THREE.JS 原始碼檔案中Core/BufferGeometry.js檔案的註釋.


/**
 * @author alteredq / http://alteredqualia.com/
 */
/*
///BufferGeometry類用來和BufferAttribute配合使用,更多細節可以參考官方的樣例http://threejs.org/
/// 這個類是另一種建立幾何體物件的方式,它將所有的資料包括頂點位置,法線,面,顏色,uv和其它的自定義屬性存在緩衝區,
/// 這樣可以減少GPU的負荷,BufferGeometry同樣也比Geometry物件複雜,增加了使用的難度,這裡的屬性都是存放在陣列中,
/// 比如頂點位置不是Vector3物件,顏色也不是color物件,而是陣列.需要訪問這些屬性,需要從屬性緩衝區中讀原始資料.
/// NOTE:根據BufferGeometry類特性,我們在建立一些靜態物件,例項化後不經常操作的物件時,選擇這個類.
///
///
///Example
			var geometry = new THREE.BufferGeometry(); 

			// create a simple square shape. We duplicate the top left and bottom right 
			// vertices because each vertex needs to appear once per triangle. 

			var vertexPositions = [ [-1.0, -1.0, 1.0], 
									[ 1.0, -1.0, 1.0], 
									[ 1.0, 1.0, 1.0], 
									[ 1.0, 1.0, 1.0], 
									[-1.0, 1.0, 1.0], 
									[-1.0, -1.0, 1.0] ]; 

			var vertices = new Float32Array( vertexPositions.length * 3 ); 
			// three components per vertex 
			// components of the position vector for each vertex are stored 
			// contiguously in the buffer. 

			for ( var i = 0; i < vertexPositions.length; i++ ) { 
				vertices[ i*3 + 0 ] = vertexPositions[i][0]; 
				vertices[ i*3 + 1 ] = vertexPositions[i][1]; 
				vertices[ i*3 + 2 ] = vertexPositions[i][2]; 
			} 

			// itemSize = 3 because there are 3 values (components) per vertex 

			geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); 
			var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); 
			var mesh = new THREE.Mesh( geometry, material );
///
///
///
*/
///<summary>BufferGeometry</summary>
///<returns type="BufferGeometry">返回在緩衝區建立的幾何體物件</returns>
THREE.BufferGeometry = function () {

	this.id = THREE.GeometryIdCount ++;	//BufferGeometryy物件id屬性.
	this.uuid = THREE.Math.generateUUID();	//呼叫THREE.Math.generateUUID()方法,BufferGeometry物件uuid(通用唯一識別碼)屬性,

	this.name = '';		//BufferGeometry物件name屬性,可以為當前物件定義一個名稱,初始化為''

	this.attributes = {};	//BufferGeometry物件attributes屬性,可以為當前物件定義一個名稱,初始化為{}
	this.drawcalls = [];	//WebGL分割槽塊繪製,存放區塊的陣列
	this.offsets = this.drawcalls; // backwards compatibility 向後相容
									//WebGL分割槽塊繪製,存放區塊的陣列

	this.boundingBox = null;	//立方體界限,根據當前幾何體生成的立方體界限	{ min: new THREE.Vector3(), max: new THREE.Vector3() }
	this.boundingSphere = null;	//球體界限,根據當前幾何體生成的球體界限	{ radius: float }

};

/****************************************
****下面是BufferGeometry物件提供的功能函式.
****************************************/
THREE.BufferGeometry.prototype = {

	constructor: THREE.BufferGeometry,	//構造器,返回對建立此物件的BufferGeometry函式的引用

	/*
	///addAttribute方法給BufferGeometry物件新增屬性資訊.
	*/
	///<summary>addAttribute</summary>
	///<param name ="name" type="String">屬性名稱</param>
	///<param name ="attribute" type="THREE.BufferAttribute">屬性物件</param>
	///<returns type="Object3D">返回新增過屬性BufferGeometry物件</returns>
	addAttribute: function ( name, attribute ) {

		if ( attribute instanceof THREE.BufferAttribute === false ) {	//如果引數attribute不是THREE.BufferAttribute物件
			//提示使用者引數錯誤.使用正確的物件
			console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' );

			this.attributes[ name ] = { array: arguments[ 1 ], itemSize: arguments[ 2 ] };

			return;		//直接返回

		}

		this.attributes[ name ] = attribute;	//返回新增過屬性BufferGeometry物件

	},

	/*
	///getAttribute方法根據屬性名(引數name)返回BufferGeometry物件的屬性資訊.
	*/
	///<summary>getAttribute</summary>
	///<param name ="name" type="String">屬性名稱</param>
	///<returns type="THREE.BufferAttribute">返回BufferGeometry物件的屬性</returns>
	getAttribute: function ( name ) {

		return this.attributes[ name ];	//返回BufferGeometry物件的屬性

	},

	/*
	///addDrawCall方法將使用三角面構建的BufferGeometry物件.拆分成多個部分多次呼叫WebGL繪製,每次呼叫WebGL繪製將使用當前幾何體的頂點陣列的子集來配置著色器.
	///這種做法非常有意義,比如你的幾何體物件有65535個以上的頂點,WebGL將一次繪製呼叫的最大頂點限制為65535個.如果將物件拆分成幾部分,就不會出問題了.
	///被拆分的每部分生成這樣一種結構組成的陣列:{ start: Integer, count: Integer, index: Integer } 
	///start重新指定在繪製呼叫第一個頂點索引,
	///count指定多少個頂點都包括在內,
	///index指定一個可選的偏移。
	///NOTE:使用adddrawcall新增繪製呼叫,而不是直接修改此陣列。
	*/
	///<summary>addDrawCall</summary>
	///<param name ="start" type="Number">start重新指定在繪製呼叫第一個頂點索引,</param>
	///<param name ="count" type="Number">count指定多少個頂點都包括在內,</param>
	///<param name ="indexOffset" type="Number">index指定一個可選的偏移。</param>
	///<returns type="Array">返回{ start: Integer, count: Integer, index: Integer } 結構組成的陣列</returns>
	addDrawCall: function ( start, count, indexOffset ) {

		this.drawcalls.push( {

			start: start,	//start重新指定在繪製呼叫第一個頂點索引,
			count: count,	//count指定多少個頂點都包括在內,
			index: indexOffset !== undefined ? indexOffset : 0 //index指定一個可選的偏移。

		} );

	},

	/*
	///applyMatrix方法對BufferGeometry物件的vertices頂點應用矩陣變換.達到旋轉,縮放,移動的目的.
	*/
	///<summary>applyMatrix</summary>
	///<param name ="matrix" type="Matrix4">仿射矩陣</param>
	///<returns type="BufferGeometry">返回新的BufferGeometry物件</returns>
	applyMatrix: function ( matrix ) {

		var position = this.attributes.position;

		if ( position !== undefined ) {

			matrix.applyToVector3Array( position.array );
			position.needsUpdate = true;	// 將屬性position.needsUpdate設定為true

		}

		var normal = this.attributes.normal;

		if ( normal !== undefined ) {

			var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );

			normalMatrix.applyToVector3Array( normal.array );	//對屬性陣列中的數值應用變換
			normal.needsUpdate = true;

		}

	},

	/*
	///fromGeometry方法將Geometry物件轉換為BufferGeometry物件.
	///NOTE:settings引數的取值範圍是THREE.NoColors || THREE.FaceColors || THREE.VertexColors
	*/
	///<summary>fromGeometry</summary>
	///<param name ="geometry" type="Geometry">Geometry物件</param>
	///<param name ="settings" type=" { 'vertexColors': THREE.NoColors || THREE.FaceColors || THREE.VertexColors}">可選引數,用來設定轉換後的物件頂點從哪裡繼承顏色.</param>
	///<returns type="BufferGeometry">返回新的BufferGeometry物件</returns>
	fromGeometry: function ( geometry, settings ) {

		settings = settings || { 'vertexColors': THREE.NoColors };

		var vertices = geometry.vertices;
		var faces = geometry.faces;
		var faceVertexUvs = geometry.faceVertexUvs;
		var vertexColors = settings.vertexColors;
		var hasFaceVertexUv = faceVertexUvs[ 0 ].length > 0;
		var hasFaceVertexNormals = faces[ 0 ].vertexNormals.length == 3;

		var positions = new Float32Array( faces.length * 3 * 3 );
		this.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );

		var normals = new Float32Array( faces.length * 3 * 3 );
		this.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );

		if ( vertexColors !== THREE.NoColors ) {	//如果沒有設定settings引數

			var colors = new Float32Array( faces.length * 3 * 3 );	//將Geometry物件的三角面的所有頂點顏色一一轉換為BufferGeometry物件的屬性格式
			this.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );

		}

		if ( hasFaceVertexUv === true ) {

			var uvs = new Float32Array( faces.length * 3 * 2 );
			this.addAttribute( 'uvs', new THREE.BufferAttribute( uvs, 2 ) );

		}

		for ( var i = 0, i2 = 0, i3 = 0; i < faces.length; i ++, i2 += 6, i3 += 9 ) {	//遍歷Geometry物件的三角面數組

			var face = faces[ i ];

			var a = vertices[ face.a ];
			var b = vertices[ face.b ];
			var c = vertices[ face.c ];

			positions[ i3     ] = a.x;	//將位置資訊轉換
			positions[ i3 + 1 ] = a.y;
			positions[ i3 + 2 ] = a.z;

			positions[ i3 + 3 ] = b.x;
			positions[ i3 + 4 ] = b.y;
			positions[ i3 + 5 ] = b.z;

			positions[ i3 + 6 ] = c.x;
			positions[ i3 + 7 ] = c.y;
			positions[ i3 + 8 ] = c.z;

			if ( hasFaceVertexNormals === true ) {	//將頂點法線轉換

				var na = face.vertexNormals[ 0 ];
				var nb = face.vertexNormals[ 1 ];
				var nc = face.vertexNormals[ 2 ];

				normals[ i3     ] = na.x;
				normals[ i3 + 1 ] = na.y;
				normals[ i3 + 2 ] = na.z;

				normals[ i3 + 3 ] = nb.x;
				normals[ i3 + 4 ] = nb.y;
				normals[ i3 + 5 ] = nb.z;

				normals[ i3 + 6 ] = nc.x;
				normals[ i3 + 7 ] = nc.y;
				normals[ i3 + 8 ] = nc.z;

			} else {

				var n = face.normal;	//將三角面法線轉換

				normals[ i3     ] = n.x;
				normals[ i3 + 1 ] = n.y;
				normals[ i3 + 2 ] = n.z;

				normals[ i3 + 3 ] = n.x;
				normals[ i3 + 4 ] = n.y;
				normals[ i3 + 5 ] = n.z;

				normals[ i3 + 6 ] = n.x;
				normals[ i3 + 7 ] = n.y;
				normals[ i3 + 8 ] = n.z;

			}

			if ( vertexColors === THREE.FaceColors ) {

				var fc = face.color;

				colors[ i3     ] = fc.r;
				colors[ i3 + 1 ] = fc.g;
				colors[ i3 + 2 ] = fc.b;

				colors[ i3 + 3 ] = fc.r;
				colors[ i3 + 4 ] = fc.g;
				colors[ i3 + 5 ] = fc.b;

				colors[ i3 + 6 ] = fc.r;
				colors[ i3 + 7 ] = fc.g;
				colors[ i3 + 8 ] = fc.b;

			} else if ( vertexColors === THREE.VertexColors ) {	//將頂點顏色轉換

				var vca = face.vertexColors[ 0 ];
				var vcb = face.vertexColors[ 1 ];
				var vcc = face.vertexColors[ 2 ];

				colors[ i3     ] = vca.r;
				colors[ i3 + 1 ] = vca.g;
				colors[ i3 + 2 ] = vca.b;

				colors[ i3 + 3 ] = vcb.r;
				colors[ i3 + 4 ] = vcb.g;
				colors[ i3 + 5 ] = vcb.b;

				colors[ i3 + 6 ] = vcc.r;
				colors[ i3 + 7 ] = vcc.g;
				colors[ i3 + 8 ] = vcc.b;

			}

			if ( hasFaceVertexUv === true ) {	//將頂點uv轉換

				var uva = faceVertexUvs[ 0 ][ i ][ 0 ];
				var uvb = faceVertexUvs[ 0 ][ i ][ 1 ];
				var uvc = faceVertexUvs[ 0 ][ i ][ 2 ];

				uvs[ i2     ] = uva.x;
				uvs[ i2 + 1 ] = uva.y;

				uvs[ i2 + 2 ] = uvb.x;
				uvs[ i2 + 3 ] = uvb.y;

				uvs[ i2 + 4 ] = uvc.x;
				uvs[ i2 + 5 ] = uvc.y;

			}

		}

		this.computeBoundingSphere()	//重新計算當前物件的球體界限

		return this;	//返回新的BufferGeometry物件.

	},

	/*
	///computeBoundingBox方法重新計算當前幾何體物件的立方體界限,並更新this.boundingBox屬性.
	*/
	///<summary>computeBoundingBox</summary>
	///<returns type="Number">返回重算立方體界限後的幾何體物件</returns>
	computeBoundingBox: function () {

		if ( this.boundingBox === null ) {

			this.boundingBox = new THREE.Box3();

		}

		var positions = this.attributes[ 'position' ].array;

		if ( positions ) {

			var bb = this.boundingBox;

			if ( positions.length >= 3 ) {
				bb.min.x = bb.max.x = positions[ 0 ];
				bb.min.y = bb.max.y = positions[ 1 ];
				bb.min.z = bb.max.z = positions[ 2 ];
			}

			for ( var i = 3, il = positions.length; i < il; i += 3 ) {	//獲得當前位置屬性陣列中的x,y,z座標的最大最小值,得到立方體界限

				var x = positions[ i ];
				var y = positions[ i + 1 ];
				var z = positions[ i + 2 ];

				// bounding box
				// 立方體界限

				if ( x < bb.min.x ) {

					bb.min.x = x;

				} else if ( x > bb.max.x ) {

					bb.max.x = x;

				}

				if ( y < bb.min.y ) {

					bb.min.y = y;

				} else if ( y > bb.max.y ) {

					bb.max.y = y;

				}

				if ( z < bb.min.z ) {

					bb.min.z = z;

				} else if ( z > bb.max.z ) {

					bb.max.z = z;

				}

			}

		}

		if ( positions === undefined || positions.length === 0 ) {

			this.boundingBox.min.set( 0, 0, 0 );
			this.boundingBox.max.set( 0, 0, 0 );

		}

		if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {
			//如果position陣列沒有值,提示使用者.
			console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.' );

		}

	},

	/*
	///computeBoundingSphere方法重新計算當前幾何體物件的球體界限,並更新this.boundingSphere[]屬性.
	*/
	///<summary>computeBoundingSphere</summary>
	///<returns type="Number">返回重算球體界限後的幾何體物件</returns>
	computeBoundingSphere: function () {

		var box = new THREE.Box3();
		var vector = new THREE.Vector3();

		return function () {

			if ( this.boundingSphere === null ) {

				this.boundingSphere = new THREE.Sphere();

			}

			var positions = this.attributes[ 'position' ].array;

			if ( positions ) {

				box.makeEmpty();

				var center = this.boundingSphere.center;

				for ( var i = 0, il = positions.length; i < il; i += 3 ) {

					vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] );
					box.expandByPoint( vector );

				}

				box.center( center );

				// hoping to find a boundingSphere with a radius smaller than the
				// boundingSphere of the boundingBox:  sqrt(3) smaller in the best case

				var maxRadiusSq = 0;

				for ( var i = 0, il = positions.length; i < il; i += 3 ) {

					vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] );
					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );

				}

				this.boundingSphere.radius = Math.sqrt( maxRadiusSq );	//計算當前幾何體物件的球體界限並更新this.boundingSphere[]屬性.

				if ( isNaN( this.boundingSphere.radius ) ) {

					console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.' );

				}

			}

		}

	}(),

	computeFaceNormals: function () {

		// backwards compatibility
		//方法已刪除, 向後相容

	},

	/*
	///computeVertexNormals方法重新計算BufferGeometry物件的三角面對象頂點的法線向量,face陣列中每個元素的vertexNormal屬性,一個face3型物件有3個,一個face4型物件有4個,
	/// 但是需要注意的是,被多個表面共享的頂點,其法線向量只有一個,同時受到多個表面的影響。比如中心在原點,三組表面都垂直於軸的立方體,
	/// 其第一象限中的頂點,法線向量是(1,1,1)的歸一化。雖然看上去不可思議,平面的頂點的法線居然不是垂直於平面的,但這種指定法線的方法
	/// 在利用平面模擬曲面的時候有很好的效果。
	///
	/// 定義:頂點法線(Vertex Normal)是過頂點的一個向量,用於在高洛德著色(Gouraud Shading)中的計算光照和紋理效果。在生成曲面時,
	///		 通常令頂點法線和相鄰平面的法線保持等角,如圖1,這樣進行渲染時,會在平面接縫處產生一種平滑過渡的效果。如果是多邊形,
	///		 則令頂點法線等於該點所屬平面(三角形)的法線,如圖2,以便在接縫處產生突出的邊緣。
	/// 
	/// 參考:http://en.wikipedia.org/wiki/Vertex_normal
	/// 參考:http://blog.csdn.net/sophistcxf/article/details/9095911
	/// 參考:http://www.cnblogs.com/flying_bat/archive/2007/10/13/923286.html
	*/
	///<summary>computeVertexNormals</summary>
	///<returns type="BufferGeometry">返回重演算法線向量後的幾何體物件</returns>
	computeVertexNormals: function () {

		if ( this.attributes[ 'position' ] ) {

			var i, il;
			var j, jl;

			var nVertexElements = this.attributes[ 'position' ].array.length;

			if ( this.attributes[ 'normal' ] === undefined ) {

				this.attributes[ 'normal' ] = {

					itemSize: 3,
					array: new Float32Array( nVertexElements )

				};

			} else {

				// reset existing normals to zero
				// 將現有的法線向量重置為0

				for ( i = 0, il = this.attributes[ 'normal' ].array.length; i < il; i ++ ) {

					this.attributes[ 'normal' ].array[ i ] = 0;

				}

			}

			var positions = this.attributes[ 'position' ].array;
			var normals = this.attributes[ 'normal' ].array;

			var vA, vB, vC, x, y, z,

			pA = new THREE.Vector3(),
			pB = new THREE.Vector3(),
			pC = new THREE.Vector3(),

			cb = new THREE.Vector3(),
			ab = new THREE.Vector3();

			// indexed elements
			// 元素索引

			if ( this.attributes[ 'index' ] ) {

				var indices = this.attributes[ 'index' ].array;

				var offsets = ( this.offsets.length > 0 ? this.offsets : [ { start: 0, count: indices.length, index: 0 } ] );

				for ( j = 0, jl = offsets.length; j < jl; ++ j ) {

					var start = offsets[ j ].start;
					var count = offsets[ j ].count;
					var index = offsets[ j ].index;

					for ( i = start, il = start + count; i < il; i += 3 ) {

						vA = index + indices[ i ];
						vB = index + indices[ i + 1 ];
						vC = index + indices[ i + 2 ];

						x = positions[ vA * 3 ];
						y = positions[ vA * 3 + 1 ];
						z = positions[ vA * 3 + 2 ];
						pA.set( x, y, z );

						x = positions[ vB * 3 ];
						y = positions[ vB * 3 + 1 ];
						z = positions[ vB * 3 + 2 ];
						pB.set( x, y, z );

						x = positions[ vC * 3 ];
						y = positions[ vC * 3 + 1 ];
						z = positions[ vC * 3 + 2 ];
						pC.set( x, y, z );

						cb.subVectors( pC, pB );
						ab.subVectors( pA, pB );
						cb.cross( ab );

						normals[ vA * 3     ] += cb.x;
						normals[ vA * 3 + 1 ] += cb.y;
						normals[ vA * 3 + 2 ] += cb.z;

						normals[ vB * 3     ] += cb.x;
						normals[ vB * 3 + 1 ] += cb.y;
						normals[ vB * 3 + 2 ] += cb.z;

						normals[ vC * 3     ] += cb.x;
						normals[ vC * 3 + 1 ] += cb.y;
						normals[ vC * 3 + 2 ] += cb.z;

					}

				}

			// non-indexed elements (unconnected triangle soup)
			// 沒有索引的元素,說明不是三角面對象.

			} else {

				for ( i = 0, il = positions.length; i < il; i += 9 ) {

					x = positions[ i ];
					y = positions[ i + 1 ];
					z = positions[ i + 2 ];
					pA.set( x, y, z );

					x = positions[ i + 3 ];
					y = positions[ i + 4 ];
					z = positions[ i + 5 ];
					pB.set( x, y, z );

					x = positions[ i + 6 ];
					y = positions[ i + 7 ];
					z = positions[ i + 8 ];
					pC.set( x, y, z );

					cb.subVectors( pC, pB );
					ab.subVectors( pA, pB );
					cb.cross( ab );

					normals[ i     ] = cb.x;
					normals[ i + 1 ] = cb.y;
					normals[ i + 2 ] = cb.z;

					normals[ i + 3 ] = cb.x;
					normals[ i + 4 ] = cb.y;
					normals[ i + 5 ] = cb.z;

					normals[ i + 6 ] = cb.x;
					normals[ i + 7 ] = cb.y;
					normals[ i + 8 ] = cb.z;

				}

			}

			this.normalizeNormals();	//獲得當前定點的規範化法線向量

			this.normalsNeedUpdate = true;	//設定this.normalsNeedUpdate屬性為true

		}

	},

	/*
	///computeTangents方法重新計算BufferGeometry物件三角面對象頂點的切線空間,
	///TBN切線是一個有方向的單位長度,沿著網格表面指向水平(U)紋理方向。在Three.js中切線被描述為Vector4,包括x,y,z這些元件定義的向量,
	///如果需要,及w用來翻轉副法線。
	///
	/// 參考:http://blog.csdn.net/bonchoix/article/details/8619624
	/// 參考:http://azykise.blog.163.com/blog/static/1730802442010431113010224/
	/// 參考:http://blog.csdn.net/meegomeego/article/details/8605463
	/// 參考:http://shiba.hpe.sh.cn/jiaoyanzu/wuli/showArticle.aspx?articleId=474&classId=4
	///
	/// NOTE:幾何體Geometry物件必須有vertex UV屬性,0層將被使用.
	*/
	///<summary>computeTangents</summary>
	///<returns type="Vector3">返回重算切線空間後的幾何體物件</returns>
	computeTangents: function () {

		// based on http://www.terathon.com/code/tangent.html
		// (per vertex tangents)

		if ( this.attributes[ 'index' ] === undefined ||
			 this.attributes[ 'position' ] === undefined ||
			 this.attributes[ 'normal' ] === undefined ||
			 this.attributes[ 'uv' ] === undefined ) {
			//提示使用者有index, position, normal or uv屬性,才能計算切線空間
			console.warn( 'Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()' );
			return;	//退出

		}
		//將陣列中的元素轉換成頂點,法線向量,uv等物件
		var indices = this.attributes[ 'index' ].array;
		var positions = this.attributes[ 'position' ].array;
		var normals = this.attributes[ 'normal' ].array;
		var uvs = this.attributes[ 'uv' ].array;

		var nVertices = positions.length / 3;

		if ( this.attributes[ 'tangent' ] === undefined ) {

			var nTangentElements = 4 * nVertices;

			this.attributes[ 'tangent' ] = {

				itemSize: 4,
				array: new Float32Array( nTangentElements )

			};

		}

		var tangents = this.attributes[ 'tangent' ].array;

		var tan1 = [], tan2 = [];

		for ( var k = 0; k < nVertices; k ++ ) {

			tan1[ k ] = new THREE.Vector3();
			tan2[ k ] = new THREE.Vector3();

		}

		var xA, yA, zA,
			xB, yB, zB,
			xC, yC, zC,

			uA, vA,
			uB, vB,
			uC, vC,

			x1, x2, y1, y2, z1, z2,
			s1, s2, t1, t2, r;

		var sdir = new THREE.Vector3(), tdir = new THREE.Vector3();
		/*
		///handleTriangle方法重新計算三角面對象的切線空間的TB,TBN.
		*/
		///<summary>handleTriangle</summary>
		///<param name ="a" type="Number">三角面角點a的索引</param>
		///<param name ="b" type="Number">三角面角點b的索引</param>
		///<param name ="c" type="Number">三角面角點c的索引</param>
		function handleTriangle( a, b, c ) {

			xA = positions[ a * 3 ];
			yA = positions[ a * 3 + 1 ];
			zA = positions[ a * 3 + 2 ];

			xB = positions[ b * 3 ];
			yB = positions[ b * 3 + 1 ];
			zB = positions[ b * 3 + 2 ];

			xC = positions[ c * 3 ];
			yC = positions[ c * 3 + 1 ];
			zC = positions[ c * 3 + 2 ];

			uA = uvs[ a * 2 ];
			vA = uvs[ a * 2 + 1 ];

			uB = uvs[ b * 2 ];
			vB = uvs[ b * 2 + 1 ];

			uC = uvs[ c * 2 ];
			vC = uvs[ c * 2 + 1 ];

			x1 = xB - xA;
			x2 = xC - xA;

			y1 = yB - yA;
			y2 = yC - yA;

			z1 = zB - zA;
			z2 = zC - zA;

			s1 = uB - uA;
			s2 = uC - uA;

			t1 = vB - vA;
			t2 = vC - vA;

			r = 1.0 / ( s1 * t2 - s2 * t1 );

			sdir.set(
				( t2 * x1 - t1 * x2 ) * r,
				( t2 * y1 - t1 * y2 ) * r,
				( t2 * z1 - t1 * z2 ) * r
			);

			tdir.set(
				( s1 * x2 - s2 * x1 ) * r,
				( s1 * y2 - s2 * y1 ) * r,
				( s1 * z2 - s2 * z1 ) * r
			);

			tan1[ a ].add( sdir );
			tan1[ b ].add( sdir );
			tan1[ c ].add( sdir );

			tan2[ a ].add( tdir );
			tan2[ b ].add( tdir );
			tan2[ c ].add( tdir );

		}

		var i, il;
		var j, jl;
		var iA, iB, iC;

		var offsets = this.offsets;

		for ( j = 0, jl = offsets.length; j < jl; ++ j ) {

			var start = offsets[ j ].start;
			var count = offsets[ j ].count;
			var index = offsets[ j ].index;

			for ( i = start, il = start + count; i < il; i += 3 ) {

				iA = index + indices[ i ];
				iB = index + indices[ i + 1 ];
				iC = index + indices[ i + 2 ];

				handleTriangle( iA, iB, iC );

			}

		}

		var tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3();
		var n = new THREE.Vector3(), n2 = new THREE.Vector3();
		var w, t, test;
		/*
		///handleVertex方法重新計算幾何體物件頂點的切線空間的TB,TBN.
		*/
		///<summary>handleVertex</summary>
		///<param name ="v" type="Number">頂點a的索引</param>
		function handleVertex( v ) {

			n.x = normals[ v * 3 ];
			n.y = normals[ v * 3 + 1 ];
			n.z = normals[ v * 3 + 2 ];

			n2.copy( n );

			t = tan1[ v ];

			// Gram-Schmidt orthogonalize

			tmp.copy( t );
			tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();

			// Calculate handedness

			tmp2.crossVectors( n2, t );
			test = tmp2.dot( tan2[ v ] );
			w = ( test < 0.0 ) ? - 1.0 : 1.0;

			tangents[ v * 4     ] = tmp.x;
			tangents[ v * 4 + 1 ] = tmp.y;
			tangents[ v * 4 + 2 ] = tmp.z;
			tangents[ v * 4 + 3 ] = w;

		}

		for ( j = 0, jl = offsets.length; j < jl; ++ j ) {	//遍歷頂點陣列

			var start = offsets[ j ].start;
			var count = offsets[ j ].count;
			var index = offsets[ j ].index;

			for ( i = start, il = start + count; i < il; i += 3 ) {

				iA = index + indices[ i ];
				iB = index + indices[ i + 1 ];
				iC = index + indices[ i + 2 ];

				handleVertex( iA );	//設定頂點的法線向量
				handleVertex( iB );
				handleVertex( iC );

			}

		}

	},

	/*
	///computeOffsets方法將較大的幾何體物件的頂點數限制在indexBufferSize所設定的大小,預設為65535.
	*/
	///<summary>computeTangents</summary>
	///<returns type="Vector3">返回{ start: Integer, count: Integer, index: Integer } 結構組成的陣列</returns>
	/*
		computeOffsets
		計算偏移量
		Compute the draw offset for large models by chunking the index buffer into chunks of 65k addressable vertices.
		當遇到比較大的幾何體物件,根據物件的索引值將物件拆分成預設尺寸為65535的塊,供WebGL繪製圖形使用,
		This method will effectively rewrite the index buffer and remap all attributes to match the new indices.
		這個方法非常對拆分模型非常的游泳.
		WARNING: This method will also expand the vertex count to prevent sprawled triangles across draw offsets.
		注意: 這個方法還可以通過更改indexBufferSize的值,防止不完整的三角面,
		indexBufferSize - Defaults to 65535, but allows for larger or smaller chunks.
		indexBufferSize 屬性預設大小事65535,但可以更大或更小.
	*/
	computeOffsets: function ( indexBufferSize ) {

		var size = indexBufferSize;
		if ( indexBufferSize === undefined )
			size = 65535; //WebGL limits type of index buffer values to 16-bit. 
						  //將WebGL繪製界限限定在16位大小

		var s = Date.now();

		var indices = this.attributes[ 'index' ].array;
		var vertices = this.attributes[ 'position' ].array;

		var verticesCount = ( vertices.length / 3 );
		var facesCount = ( indices.length / 3 );

		/*
		console.log("Computing buffers in offsets of "+size+" -> indices:"+indices.length+" vertices:"+vertices.length);
		console.log("Faces to process: "+(indices.length/3));
		console.log("Reordering "+verticesCount+" vertices.");
		*/

		var sortedIndices = new Uint16Array( indices.length ); //16-bit buffers
		var indexPtr = 0;
		var vertexPtr = 0;

		var offsets = [ { start:0, count:0, index:0 } ];
		var offset = offsets[ 0 ];

		var duplicatedVertices = 0;
		var newVerticeMaps = 0;
		var faceVertices = new Int32Array( 6 );
		var vertexMap = new Int32Array( vertices.length );
		var revVertexMap = new Int32Array( vertices.length );
		for ( var j = 0; j < vertices.length; j ++ ) { vertexMap[ j ] = - 1; revVertexMap[ j ] = - 1; }

		/*
			Traverse every face and reorder vertices in the proper offsets of 65k.
			遍歷所有三角面的頂點,將頂點以65k作為界限劃分.
			We can have more than 65k entries in the index buffer per offset, but only reference 65k values.
			我們可以講緩衝區塊劃分出比65535跟多的頂點數,但是推薦65535
		*/
		for ( var findex = 0; findex < facesCount; findex ++ ) {
			newVerticeMaps = 0;

			for ( var vo = 0; vo < 3; vo ++ ) {
				var vid = indices[ findex * 3 + vo ];
				if ( vertexMap[ vid ] == - 1 ) {
					//Unmapped vertice
					//沒有對映的頂點
					faceVertices[ vo * 2 ] = vid;
					faceVertices[ vo * 2 + 1 ] = - 1;
					newVerticeMaps ++;
				} else if ( vertexMap[ vid ] < offset.index ) {
					//Reused vertices from previous block (duplicate)
					//複用之前區塊的頂點
					faceVertices[ vo * 2 ] = vid;
					faceVertices[ vo * 2 + 1 ] = - 1;
					duplicatedVertices ++;
				} else {
					//Reused vertice in the current block
					//在當前的區塊兒內複用頂點
					faceVertices[ vo * 2 ] = vid;
					faceVertices[ vo * 2 + 1 ] = vertexMap[ vid ];
				}
			}

			var faceMax = vertexPtr + newVerticeMaps;
			if ( faceMax > ( offset.index + size ) ) {
				var new_offset = { start:indexPtr, count:0, index:vertexPtr };
				offsets.push( new_offset );
				offset = new_offset;

				//Re-evaluate reused vertices in light of new offset.
				//重新評估複用新區塊的頂點
				for ( var v = 0; v < 6; v += 2 ) {
					var new_vid = faceVertices[ v + 1 ];
					if ( new_vid > - 1 && new_vid < offset.index )
						faceVertices[ v + 1 ] = - 1;
				}
			}

			//Reindex the face.
			//重新索引三角面
			for ( var v = 0; v < 6; v += 2 ) {
				var vid = faceVertices[ v ];
				var new_vid = faceVertices[ v + 1 ];

				if ( new_vid === - 1 )
					new_vid = vertexPtr ++;

				vertexMap[ vid ] = new_vid;
				revVertexMap[ new_vid ] = vid;
				sortedIndices[ indexPtr ++ ] = new_vid - offset.index; //XXX overflows at 16bit
				offset.count ++;
			}
		}

		/* Move all attribute values to map to the new computed indices , also expand the vertice stack to match our new vertexPtr. */
		//將新計算的索引對映到所有屬性值,並且擴充套件頂點堆疊匹配新的vertexPtr
		this.reorderBuffers( sortedIndices, revVertexMap, vertexPtr );
		this.offsets = offsets;	//重新設定this.offsets屬性

		/*
		var orderTime = Date.now();
		console.log("Reorder time: "+(orderTime-s)+"ms");
		console.log("Duplicated "+duplicatedVertices+" vertices.");
		console.log("Compute Buffers time: "+(Date.now()-s)+"ms");
		console.log("Draw offsets: "+offsets.length);
		*/

		return offsets;	//返回重算的offsets屬性.
	},

	/*
	///merge方法將兩個幾何體物件或者BufferGeometry裡面的幾何體物件合併,將屬性數組合並.
	*/
	///<summary>merge</summary>
	///<returns type="BufferGeometry">返回合併後的幾何體物件</returns>	
	merge: function () {

		console.log( 'BufferGeometry.merge(): TODO' );

	},

	normalizeNormals: function () {

		var normals = this.attributes[ 'normal' ].array;

		var x, y, z, n;

		for ( var i = 0, il = normals.length; i < il; i += 3 ) {

			x = normals[ i ];
			y = normals[ i + 1 ];
			z = normals[ i + 2 ];

			n = 1.0 / Math.sqrt( x * x + y * y + z * z );

			normals[ i     ] *= n;
			normals[ i + 1 ] *= n;
			normals[ i + 2 ] *= n;

		}

	},

	/*
		reoderBuffers:
		Reorder attributes based on a new indexBuffer and indexMap.
		indexBuffer - Uint16Array of the new ordered indices.
		indexMap - Int32Array where the position is the new vertex ID and the value the old vertex ID for each vertex.
		vertexCount - Amount of total vertices considered in this reordering (in case you want to grow the vertice stack).
	*/
	/*
	///reorderBuffers方法根引數indexBuffer和引數indexMap重新排列緩衝區中的BufferGeometry物件的屬性陣列
	*/
	///<summary>reorderBuffers</summary>
	///<param name ="indexBuffer" type="Uint16Array">新順序的索引</param>
	///<param name ="indexMap" type="Int32Array">每個頂點id和新位置和值的對映表</param>
	///<param name ="vertexCount" type="Number">頂點a的索引</param>
	///<returns type="BufferGeometry">返回合併後的幾何體物件</returns>	
	reorderBuffers: function ( indexBuffer, indexMap, vertexCount ) {

		/* Create a copy of all attributes for reordering. */
		//建立一個所有屬性的副本用來重新排序
		var sortedAttributes = {};
		var types = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array ];
		for ( var attr in this.attributes ) {
			if ( attr == 'index' )
				continue;
			var sourceArray = this.attributes[ attr ].array;
			for ( var i = 0, il = types.length; i < il; i ++ ) {
				var type = types[ i ];
				if ( sourceArray instanceof type ) {
					sortedAttributes[ attr ] = new type( this.attributes[ attr ].itemSize * vertexCount );
					break;
				}
			}
		}

		/* Move attribute positions based on the new index map */
		// 根據新的索引表移動屬性位置.
		for ( var new_vid = 0; new_vid < vertexCount; new_vid ++ ) {
			var vid = indexMap[ new_vid ];
			for ( var attr in this.attributes ) {
				if ( attr == 'index' )
					continue;
				var attrArray = this.attributes[ attr ].array;
				var attrSize = this.attributes[ attr ].itemSize;
				var sortedAttr = sortedAttributes[ attr ];
				for ( var k = 0; k < attrSize; k ++ )
					sortedAttr[ new_vid * attrSize + k ] = attrArray[ vid * attrSize + k ];
			}
		}

		/* Carry the new sorted buffers locally */
		// 更新儲存在本地緩衝區中的屬性
		this.attributes[ 'index' ].array = indexBuffer;
		for ( var attr in this.attributes ) {
			if ( attr == 'index' )
				continue;
			this.attributes[ attr ].array = sortedAttributes[ attr ];
			this.attributes[ attr ].numItems = this.attributes[ attr ].itemSize * vertexCount;
		}
	},

	/*clone方法
	///clone方法克隆一個幾何體物件,將屬性陣列分別複製.
	*/
	///<summary>clone</summary>
	///<returns type="Vector4">返回克隆的幾何體物件</returns>	
	clone: function () {

		var geometry = new THREE.BufferGeometry();

		var types = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array ];

		for ( var attr in this.attributes ) {

			var sourceAttr = this.attributes[ attr ];
			var sourceArray = sourceAttr.array;

			var attribute = {

				itemSize: sourceAttr.itemSize,
				array: null

			};

			for ( var i = 0, il = types.length; i < il; i ++ ) {

				var type = types[ i ];

				if ( sourceArray instanceof type ) {

					attribute.array = new type( sourceArray );
					break;

				}

			}

			geometry.attributes[ attr ] = attribute;

		}

		for ( var i = 0, il = this.offsets.length; i < il; i ++ ) {

			var offset = this.offsets[ i ];

			geometry.offsets.push( {

				start: offset.start,
				index: offset.index,
				count: offset.count

			} );

		}

		return geometry;	//返回克隆的幾何體物件

	},

	/*dispose方法
	///dispose方法從記憶體中刪除物件,釋放資源.
	///NOTE: 當刪除幾何體物件,不要忘記呼叫這個方法,否則會導致記憶體洩露.
	*/
	///<summary>dispose</summary>
	dispose: function () {

		this.dispatchEvent( { type: 'dispose' } );	//排程事件.

	}

};
///EventDispatcher方法應用到當前Geometry物件.
THREE.EventDispatcher.prototype.apply( THREE.BufferGeometry.prototype );



以下程式碼是THREE.JS 原始碼檔案中Core/BufferGeometry.js檔案的註釋.