Pārlūkot izejas kodu

BufferGeometry: Move computeTangents() into core. Use in GLTFLoader.

Mugen87 4 gadi atpakaļ
vecāks
revīzija
3b7656a421

+ 6 - 0
docs/api/en/core/BufferGeometry.html

@@ -207,6 +207,12 @@
 		Bounding spheres aren't computed by default. They need to be explicitly computed, otherwise they are *null*.
 		</p>
 
+		<h3>[method:null computeTangents]()</h3>
+		<p>
+		Calculates and adds a tangent attribute to this geometry.<br />
+		The computation is only supported for indexed geometries and if position, normal, and uv attributes are defined.
+		</p>
+
 		<h3>[method:null computeVertexNormals]()</h3>
 		<p>Computes vertex normals by averaging face normals.</p>
 

+ 6 - 0
docs/api/zh/core/BufferGeometry.html

@@ -197,6 +197,12 @@
 			边界球形不会默认计算,需要调用该接口指定计算边界球形,否则保持默认值 *null*。
 		</p>
 
+		<h3>[method:null computeTangents]()</h3>
+		<p>
+		Calculates and adds a tangent attribute to this geometry.<br />
+		The computation is only supported for indexed geometries and if position, normal, and uv attributes are defined.
+		</p>
+
 		<h3>[method:null computeVertexNormals]()</h3>
 		<p>通过面片法向量的平均值计算每个顶点的法向量。</p>
 

+ 0 - 8
docs/examples/en/utils/BufferGeometryUtils.html

@@ -16,14 +16,6 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null computeTangents]( [param:BufferGeometry geometry] )</h3>
-		<p>
-		geometry -- A [page:BufferGeometry BufferGeometry] instance, which must have index, position, normal, and uv attributes.<br /><br />
-
-		Calculates and adds tangent attribute to a geometry.
-
-		</p>
-
 		<h3>[method:BufferGeometry mergeBufferGeometries]( [param:Array geometries], [param:Boolean useGroups] )</h3>
 		<p>
 		geometries -- Array of [page:BufferGeometry BufferGeometry] instances.<br />

+ 0 - 8
docs/examples/zh/utils/BufferGeometryUtils.html

@@ -16,14 +16,6 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null computeTangents]( [param:BufferGeometry geometry] )</h3>
-		<p>
-		geometry -- A [page:BufferGeometry BufferGeometry] instance, which must have index, position, normal, and uv attributes.<br /><br />
-
-		Calculates and adds tangent attribute to a geometry.
-
-		</p>
-
 		<h3>[method:BufferGeometry mergeBufferGeometries]( [param:Array geometries], [param:Boolean useGroups] )</h3>
 		<p>
 		geometries -- Array of [page:BufferGeometry BufferGeometry] instances.<br />

+ 15 - 0
examples/js/loaders/GLTFLoader.js

@@ -3240,6 +3240,21 @@ THREE.GLTFLoader = ( function () {
 						? new THREE.SkinnedMesh( geometry, material )
 						: new THREE.Mesh( geometry, material );
 
+					// Fix double sided rendered models on certain mobile devices, see https://github.com/mrdoob/three.js/issues/20997#issuecomment-756082184
+
+					if ( material.isMeshStandardMaterial === true &&
+						material.side === THREE.DoubleSide &&
+						geometry.getIndex() !== null &&
+						geometry.hasAttribute( 'position' ) === true &&
+						geometry.hasAttribute( 'normal' ) === true &&
+						geometry.hasAttribute( 'uv' ) === true &&
+						geometry.hasAttribute( 'tangent' ) === false ) {
+
+						geometry.computeTangents();
+						material.vertexTangents = true;
+
+					}
+
 					if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {
 
 						// we normalize floating point skin weight array to fix malformed assets (see #15319)

+ 2 - 161
examples/js/utils/BufferGeometryUtils.js

@@ -2,167 +2,8 @@ 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.error( 'THREE.BufferGeometryUtils: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' );
-			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.setAttribute( 'tangent', new THREE.BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) );
-
-		}
-
-		var tangents = attributes.tangent.array;
-
-		var tan1 = [], tan2 = [];
-
-		for ( var i = 0; i < nVertices; i ++ ) {
-
-			tan1[ i ] = new THREE.Vector3();
-			tan2[ i ] = 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 );
-
-			vB.sub( vA );
-			vC.sub( vA );
-
-			uvB.sub( uvA );
-			uvC.sub( uvA );
-
-			var r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y );
-
-			// silently ignore degenerate uv triangles having coincident or colinear vertices
-
-			if ( ! isFinite( r ) ) return;
-
-			sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r );
-			tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( 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 i = 0, il = groups.length; i < il; ++ i ) {
-
-			var group = groups[ i ];
-
-			var start = group.start;
-			var count = group.count;
-
-			for ( var j = start, jl = start + count; j < jl; j += 3 ) {
-
-				handleTriangle(
-					indices[ j + 0 ],
-					indices[ j + 1 ],
-					indices[ j + 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 i = 0, il = groups.length; i < il; ++ i ) {
-
-			var group = groups[ i ];
-
-			var start = group.start;
-			var count = group.count;
-
-			for ( var j = start, jl = start + count; j < jl; j += 3 ) {
-
-				handleVertex( indices[ j + 0 ] );
-				handleVertex( indices[ j + 1 ] );
-				handleVertex( indices[ j + 2 ] );
-
-			}
-
-		}
+		geometry.computeTangents();
+		console.warn( 'THREE.BufferGeometryUtils: .computeTangents() has been removed. Use BufferGeometry.computeTangents() instead.' );
 
 	},
 

+ 15 - 0
examples/jsm/loaders/GLTFLoader.js

@@ -3305,6 +3305,21 @@ var GLTFLoader = ( function () {
 						? new SkinnedMesh( geometry, material )
 						: new Mesh( geometry, material );
 
+					// Fix double sided rendered models on certain mobile devices, see https://github.com/mrdoob/three.js/issues/20997#issuecomment-756082184
+
+					if ( material.isMeshStandardMaterial === true &&
+						material.side === DoubleSide &&
+						geometry.getIndex() !== null &&
+						geometry.hasAttribute( 'position' ) === true &&
+						geometry.hasAttribute( 'normal' ) === true &&
+						geometry.hasAttribute( 'uv' ) === true &&
+						geometry.hasAttribute( 'tangent' ) === false ) {
+
+						geometry.computeTangents();
+						material.vertexTangents = true;
+
+					}
+
 					if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {
 
 						// we normalize floating point skin weight array to fix malformed assets (see #15319)

+ 0 - 1
examples/jsm/utils/BufferGeometryUtils.d.ts

@@ -2,7 +2,6 @@ import { BufferAttribute, BufferGeometry, InterleavedBufferAttribute, TrianglesD
 
 export namespace BufferGeometryUtils {
 	export function mergeBufferGeometries( geometries: BufferGeometry[], useGroups?: boolean ): BufferGeometry;
-	export function computeTangents( geometry: BufferGeometry ): null;
 	export function mergeBufferAttributes( attributes: BufferAttribute[] ): BufferAttribute;
 	export function interleaveAttributes( attributes: BufferAttribute[] ): InterleavedBufferAttribute;
 	export function estimateBytesUsed( geometry: BufferGeometry ): number;

+ 4 - 476
examples/jsm/utils/BufferGeometryUtils.js

@@ -5,176 +5,15 @@ import {
 	InterleavedBufferAttribute,
 	TriangleFanDrawMode,
 	TriangleStripDrawMode,
-	TrianglesDrawMode,
-	Vector2,
-	Vector3
+	TrianglesDrawMode
 } from '../../../build/three.module.js';
 
 var 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.error( 'THREE.BufferGeometryUtils: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' );
-			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.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) );
-
-		}
-
-		var tangents = attributes.tangent.array;
-
-		var tan1 = [], tan2 = [];
-
-		for ( var i = 0; i < nVertices; i ++ ) {
-
-			tan1[ i ] = new Vector3();
-			tan2[ i ] = new Vector3();
-
-		}
-
-		var vA = new Vector3(),
-			vB = new Vector3(),
-			vC = new Vector3(),
-
-			uvA = new Vector2(),
-			uvB = new Vector2(),
-			uvC = new Vector2(),
-
-			sdir = new Vector3(),
-			tdir = new 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 );
-
-			vB.sub( vA );
-			vC.sub( vA );
-
-			uvB.sub( uvA );
-			uvC.sub( uvA );
-
-			var r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y );
-
-			// silently ignore degenerate uv triangles having coincident or colinear vertices
-
-			if ( ! isFinite( r ) ) return;
-
-			sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r );
-			tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( 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 i = 0, il = groups.length; i < il; ++ i ) {
-
-			var group = groups[ i ];
-
-			var start = group.start;
-			var count = group.count;
-
-			for ( var j = start, jl = start + count; j < jl; j += 3 ) {
-
-				handleTriangle(
-					indices[ j + 0 ],
-					indices[ j + 1 ],
-					indices[ j + 2 ]
-				);
-
-			}
-
-		}
-
-		var tmp = new Vector3(), tmp2 = new Vector3();
-		var n = new Vector3(), n2 = new 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 i = 0, il = groups.length; i < il; ++ i ) {
-
-			var group = groups[ i ];
-
-			var start = group.start;
-			var count = group.count;
-
-			for ( var j = start, jl = start + count; j < jl; j += 3 ) {
-
-				handleVertex( indices[ j + 0 ] );
-				handleVertex( indices[ j + 1 ] );
-				handleVertex( indices[ j + 2 ] );
-
-			}
-
-		}
+		geometry.computeTangents();
+		console.warn( 'THREE.BufferGeometryUtils: .computeTangents() has been removed. Use BufferGeometry.computeTangents() instead.' );
 
 	},
 
@@ -756,6 +595,7 @@ var BufferGeometryUtils = {
 						newIndices.push( index.getX( i + 1 ) );
 						newIndices.push( index.getX( i + 2 ) );
 
+
 					} else {
 
 						newIndices.push( index.getX( i + 2 ) );
@@ -789,318 +629,6 @@ var BufferGeometryUtils = {
 
 		}
 
-	},
-
-	/**
-	 * Calculates the morphed attributes of a morphed/skinned BufferGeometry.
-	 * Helpful for Raytracing or Decals.
-	 * @param {Object3D} object
-	 * @return {Object} An Object with original position/normal attributes and morphed ones.
-	 */
-	computeMorphedBufferGeometry: function ( object ) {
-
-		if ( ! object ) {
-
-			console.error( 'Please provide an object' );
-			return null;
-
-		}
-
-		if ( ! object.geometry ) {
-
-			console.error( 'Please provide an object with a geometry' );
-			return null;
-
-		}
-
-		if ( ! object.geometry.isBufferGeometry ) {
-
-			console.error( 'Geometry is not a BufferGeometry' );
-			return null;
-
-		}
-
-		var _vA = new THREE.Vector3();
-		var _vB = new THREE.Vector3();
-		var _vC = new THREE.Vector3();
-
-		var _tempA = new THREE.Vector3();
-		var _tempB = new THREE.Vector3();
-		var _tempC = new THREE.Vector3();
-
-		var _morphA = new THREE.Vector3();
-		var _morphB = new THREE.Vector3();
-		var _morphC = new THREE.Vector3();
-
-		function _calculateMorphedAttributeData(
-			object,
-			material,
-			attribute,
-			morphAttribute,
-			morphTargetsRelative,
-			a,
-			b,
-			c,
-			modifiedAttributeArray
-		) {
-
-			_vA.fromBufferAttribute( attribute, a );
-			_vB.fromBufferAttribute( attribute, b );
-			_vC.fromBufferAttribute( attribute, c );
-
-			var morphInfluences = object.morphTargetInfluences;
-
-			if ( material.morphTargets && morphAttribute && morphInfluences ) {
-
-				_morphA.set( 0, 0, 0 );
-				_morphB.set( 0, 0, 0 );
-				_morphC.set( 0, 0, 0 );
-
-				for ( var i = 0, il = morphAttribute.length; i < il; i ++ ) {
-
-					var influence = morphInfluences[ i ];
-					var morphAttribute = morphAttribute[ i ];
-
-					if ( influence === 0 ) continue;
-
-					_tempA.fromBufferAttribute( morphAttribute, a );
-					_tempB.fromBufferAttribute( morphAttribute, b );
-					_tempC.fromBufferAttribute( morphAttribute, c );
-
-					if ( morphTargetsRelative ) {
-
-						_morphA.addScaledVector( _tempA, influence );
-						_morphB.addScaledVector( _tempB, influence );
-						_morphC.addScaledVector( _tempC, influence );
-
-					} else {
-
-						_morphA.addScaledVector( _tempA.sub( _vA ), influence );
-						_morphB.addScaledVector( _tempB.sub( _vB ), influence );
-						_morphC.addScaledVector( _tempC.sub( _vC ), influence );
-
-					}
-
-				}
-
-				_vA.add( _morphA );
-				_vB.add( _morphB );
-				_vC.add( _morphC );
-
-			}
-
-			if ( object.isSkinnedMesh ) {
-
-				object.boneTransform( a, _vA );
-				object.boneTransform( b, _vB );
-				object.boneTransform( c, _vC );
-
-			}
-
-			modifiedAttributeArray[ a * 3 + 0 ] = _vA.x;
-			modifiedAttributeArray[ a * 3 + 1 ] = _vA.y;
-			modifiedAttributeArray[ a * 3 + 2 ] = _vA.z;
-			modifiedAttributeArray[ b * 3 + 0 ] = _vB.x;
-			modifiedAttributeArray[ b * 3 + 1 ] = _vB.y;
-			modifiedAttributeArray[ b * 3 + 2 ] = _vB.z;
-			modifiedAttributeArray[ c * 3 + 0 ] = _vC.x;
-			modifiedAttributeArray[ c * 3 + 1 ] = _vC.y;
-			modifiedAttributeArray[ c * 3 + 2 ] = _vC.z;
-
-		}
-
-		var geometry = object.geometry;
-		var material = object.material;
-
-		var a, b, c;
-		var index = geometry.index;
-		var positionAttribute = geometry.attributes.position;
-		var morphPosition = geometry.morphAttributes.position;
-		var morphTargetsRelative = geometry.morphTargetsRelative;
-		var normalAttribute = geometry.attributes.normal;
-		var morphNormal = geometry.morphAttributes.position;
-
-		var groups = geometry.groups;
-		var drawRange = geometry.drawRange;
-		var i, j, il, jl;
-		var group, groupMaterial;
-		var start, end;
-
-		var modifiedPosition = new Float32Array( positionAttribute.count * positionAttribute.itemSize );
-		var modifiedNormal = new Float32Array( normalAttribute.count * normalAttribute.itemSize );
-
-		if ( index !== null ) {
-
-			// indexed buffer geometry
-
-			if ( Array.isArray( material ) ) {
-
-				for ( i = 0, il = groups.length; i < il; i ++ ) {
-
-					group = groups[ i ];
-					groupMaterial = material[ group.materialIndex ];
-
-					start = Math.max( group.start, drawRange.start );
-					end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
-
-					for ( j = start, jl = end; j < jl; j += 3 ) {
-
-						a = index.getX( j );
-						b = index.getX( j + 1 );
-						c = index.getX( j + 2 );
-
-						_calculateMorphedAttributeData(
-							object,
-							groupMaterial,
-							positionAttribute,
-							morphPosition,
-							morphTargetsRelative,
-							a, b, c,
-							modifiedPosition
-						);
-
-						_calculateMorphedAttributeData(
-							object,
-							groupMaterial,
-							normalAttribute,
-							morphNormal,
-							morphTargetsRelative,
-							a, b, c,
-							modifiedNormal
-						);
-
-					}
-
-				}
-
-			} else {
-
-				start = Math.max( 0, drawRange.start );
-				end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
-
-				for ( i = start, il = end; i < il; i += 3 ) {
-
-					a = index.getX( i );
-					b = index.getX( i + 1 );
-					c = index.getX( i + 2 );
-
-					_calculateMorphedAttributeData(
-						object,
-						material,
-						positionAttribute,
-						morphPosition,
-						morphTargetsRelative,
-						a, b, c,
-						modifiedPosition
-					);
-
-					_calculateMorphedAttributeData(
-						object,
-						material,
-						normalAttribute,
-						morphNormal,
-						morphTargetsRelative,
-						a, b, c,
-						modifiedNormal
-					);
-
-				}
-
-			}
-
-		} else if ( positionAttribute !== undefined ) {
-
-			// non-indexed buffer geometry
-
-			if ( Array.isArray( material ) ) {
-
-				for ( i = 0, il = groups.length; i < il; i ++ ) {
-
-					group = groups[ i ];
-					groupMaterial = material[ group.materialIndex ];
-
-					start = Math.max( group.start, drawRange.start );
-					end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
-
-					for ( j = start, jl = end; j < jl; j += 3 ) {
-
-						a = j;
-						b = j + 1;
-						c = j + 2;
-
-						_calculateMorphedAttributeData(
-							object,
-							groupMaterial,
-							positionAttribute,
-							morphPosition,
-							morphTargetsRelative,
-							a, b, c,
-							modifiedPosition
-						);
-
-						_calculateMorphedAttributeData(
-							object,
-							groupMaterial,
-							normalAttribute,
-							morphNormal,
-							morphTargetsRelative,
-							a, b, c,
-							modifiedNormal
-						);
-
-					}
-
-				}
-
-			} else {
-
-				start = Math.max( 0, drawRange.start );
-				end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );
-
-				for ( i = start, il = end; i < il; i += 3 ) {
-
-					a = i;
-					b = i + 1;
-					c = i + 2;
-
-					_calculateMorphedAttributeData(
-						object,
-						material,
-						positionAttribute,
-						morphPosition,
-						morphTargetsRelative,
-						a, b, c,
-						modifiedPosition
-					);
-
-					_calculateMorphedAttributeData(
-						object,
-						material,
-						normalAttribute,
-						morphNormal,
-						morphTargetsRelative,
-						a, b, c,
-						modifiedNormal
-					);
-
-				}
-
-			}
-
-		}
-
-		var morphedPositionAttribute = new THREE.Float32BufferAttribute( modifiedPosition, 3 );
-		var morphedNormalAttribute = new THREE.Float32BufferAttribute( modifiedNormal, 3 );
-
-		return {
-
-			positionAttribute: positionAttribute,
-			normalAttribute: normalAttribute,
-			morphedPositionAttribute: morphedPositionAttribute,
-			morphedNormalAttribute: morphedNormalAttribute
-
-		};
-
 	}
 
 };

+ 0 - 5
src/Three.Legacy.js

@@ -1329,11 +1329,6 @@ Object.assign( BufferGeometry.prototype, {
 		console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' );
 		this.clearGroups();
 
-	},
-	computeTangents: function () {
-
-		console.warn( 'THREE.BufferGeometry: .computeTangents() has been removed.' );
-
 	},
 	computeOffsets: function () {
 

+ 5 - 0
src/core/BufferGeometry.d.ts

@@ -139,6 +139,11 @@ export class BufferGeometry extends EventDispatcher {
 	 */
 	computeBoundingSphere(): void;
 
+	/**
+	 * Computes and adds tangent attribute to this geometry.
+	 */
+	computeTangents(): void;
+
 	/**
 	 * Computes vertex normals by averaging face normals.
 	 */

+ 166 - 0
src/core/BufferGeometry.js

@@ -1,4 +1,5 @@
 import { Vector3 } from '../math/Vector3.js';
+import { Vector2 } from '../math/Vector2.js';
 import { Box3 } from '../math/Box3.js';
 import { EventDispatcher } from './EventDispatcher.js';
 import { BufferAttribute, Float32BufferAttribute, Uint16BufferAttribute, Uint32BufferAttribute } from './BufferAttribute.js';
@@ -739,6 +740,171 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
 	},
 
+	computeTangents: function () {
+
+		const index = this.index;
+		const attributes = this.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.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' );
+			return;
+
+		}
+
+		const indices = index.array;
+		const positions = attributes.position.array;
+		const normals = attributes.normal.array;
+		const uvs = attributes.uv.array;
+
+		const nVertices = positions.length / 3;
+
+		if ( attributes.tangent === undefined ) {
+
+			this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) );
+
+		}
+
+		const tangents = attributes.tangent.array;
+
+		const tan1 = [], tan2 = [];
+
+		for ( let i = 0; i < nVertices; i ++ ) {
+
+			tan1[ i ] = new Vector3();
+			tan2[ i ] = new Vector3();
+
+		}
+
+		const vA = new Vector3(),
+			vB = new Vector3(),
+			vC = new Vector3(),
+
+			uvA = new Vector2(),
+			uvB = new Vector2(),
+			uvC = new Vector2(),
+
+			sdir = new Vector3(),
+			tdir = new 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 );
+
+			vB.sub( vA );
+			vC.sub( vA );
+
+			uvB.sub( uvA );
+			uvC.sub( uvA );
+
+			const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y );
+
+			// silently ignore degenerate uv triangles having coincident or colinear vertices
+
+			if ( ! isFinite( r ) ) return;
+
+			sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r );
+			tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( 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 );
+
+		}
+
+		let groups = this.groups;
+
+		if ( groups.length === 0 ) {
+
+			groups = [ {
+				start: 0,
+				count: indices.length
+			} ];
+
+		}
+
+		for ( let i = 0, il = groups.length; i < il; ++ i ) {
+
+			const group = groups[ i ];
+
+			const start = group.start;
+			const count = group.count;
+
+			for ( let j = start, jl = start + count; j < jl; j += 3 ) {
+
+				handleTriangle(
+					indices[ j + 0 ],
+					indices[ j + 1 ],
+					indices[ j + 2 ]
+				);
+
+			}
+
+		}
+
+		const tmp = new Vector3(), tmp2 = new Vector3();
+		const n = new Vector3(), n2 = new Vector3();
+
+		function handleVertex( v ) {
+
+			n.fromArray( normals, v * 3 );
+			n2.copy( n );
+
+			const t = tan1[ v ];
+
+			// Gram-Schmidt orthogonalize
+
+			tmp.copy( t );
+			tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();
+
+			// Calculate handedness
+
+			tmp2.crossVectors( n2, t );
+			const test = tmp2.dot( tan2[ v ] );
+			const 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 ( let i = 0, il = groups.length; i < il; ++ i ) {
+
+			const group = groups[ i ];
+
+			const start = group.start;
+			const count = group.count;
+
+			for ( let j = start, jl = start + count; j < jl; j += 3 ) {
+
+				handleVertex( indices[ j + 0 ] );
+				handleVertex( indices[ j + 1 ] );
+				handleVertex( indices[ j + 2 ] );
+
+			}
+
+		}
+
+	},
+
 	computeVertexNormals: function () {
 
 		const index = this.index;