text: /**
   * @author mrdoob / http://mrdoob.com/
   */
  
  THREE.BufferGeometryUtils = {
  
  	computeTangents: function ( geometry ) {
  
  		var index = geometry.index;
  		var attributes = geometry.attributes;
  
  		// based on http://www.terathon.com/code/tangent.html
  		// (per vertex tangents)
  
  		if ( index === null ||
  			 attributes.position === undefined ||
  			 attributes.normal === undefined ||
  			 attributes.uv === undefined ) {
  
  			console.warn( 'THREE.BufferGeometry: Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()' );
  			return;
  
  		}
  
  		var indices = index.array;
  		var positions = attributes.position.array;
  		var normals = attributes.normal.array;
  		var uvs = attributes.uv.array;
  
  		var nVertices = positions.length / 3;
  
  		if ( attributes.tangent === undefined ) {
  
  			geometry.addAttribute( 'tangent', new THREE.BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) );
  
  		}
  
  		var tangents = attributes.tangent.array;
  
  		var tan1 = [], tan2 = [];
  
  		for ( var k = 0; k < nVertices; k ++ ) {
  
  			tan1[ k ] = new THREE.Vector3();
  			tan2[ k ] = new THREE.Vector3();
  
  		}
  
  		var vA = new THREE.Vector3(),
  			vB = new THREE.Vector3(),
  			vC = new THREE.Vector3(),
  
  			uvA = new THREE.Vector2(),
  			uvB = new THREE.Vector2(),
  			uvC = new THREE.Vector2(),
  
  			sdir = new THREE.Vector3(),
  			tdir = new THREE.Vector3();
  
  		function handleTriangle( a, b, c ) {
  
  			vA.fromArray( positions, a * 3 );
  			vB.fromArray( positions, b * 3 );
  			vC.fromArray( positions, c * 3 );
  
  			uvA.fromArray( uvs, a * 2 );
  			uvB.fromArray( uvs, b * 2 );
  			uvC.fromArray( uvs, c * 2 );
  
  			var x1 = vB.x - vA.x;
  			var x2 = vC.x - vA.x;
  
  			var y1 = vB.y - vA.y;
  			var y2 = vC.y - vA.y;
  
  			var z1 = vB.z - vA.z;
  			var z2 = vC.z - vA.z;
  
  			var s1 = uvB.x - uvA.x;
  			var s2 = uvC.x - uvA.x;
  
  			var t1 = uvB.y - uvA.y;
  			var t2 = uvC.y - uvA.y;
  
  			var 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 groups = geometry.groups;
  
  		if ( groups.length === 0 ) {
  
  			groups = [ {
  				start: 0,
  				count: indices.length
  			} ];
  
  		}
  
  		for ( var j = 0, jl = groups.length; j < jl; ++ j ) {
  
  			var group = groups[ j ];
  
  			var start = group.start;
  			var count = group.count;
  
  			for ( var i = start, il = start + count; i < il; i += 3 ) {
  
  				handleTriangle(
  					indices[ i + 0 ],
  					indices[ i + 1 ],
  					indices[ i + 2 ]
  				);
  
  			}
  
  		}
  
  		var tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3();
  		var n = new THREE.Vector3(), n2 = new THREE.Vector3();
  		var w, t, test;
  
  		function handleVertex( v ) {
  
  			n.fromArray( normals, v * 3 );
  			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 ( var j = 0, jl = groups.length; j < jl; ++ j ) {
  
  			var group = groups[ j ];
  
  			var start = group.start;
  			var count = group.count;
  
  			for ( var i = start, il = start + count; i < il; i += 3 ) {
  
  				handleVertex( indices[ i + 0 ] );
  				handleVertex( indices[ i + 1 ] );
  				handleVertex( indices[ i + 2 ] );
  
  			}
  
  		}
  
  	}
  
  };