Spaces:
				
			
			
	
			
			
					
		Running
		
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
	| /** | |
| * @author mrdoob / http://mrdoob.com/ | |
| */ | |
| import { | |
| BufferAttribute, | |
| BufferGeometry, | |
| InterleavedBuffer, | |
| InterleavedBufferAttribute, | |
| Vector2, | |
| Vector3 | |
| } 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.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 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 ); | |
| 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 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 ] ); | |
| } | |
| } | |
| }, | |
| /** | |
| * @param {Array<BufferGeometry>} geometries | |
| * @param {Boolean} useGroups | |
| * @return {BufferGeometry} | |
| */ | |
| mergeBufferGeometries: function ( geometries, useGroups ) { | |
| var isIndexed = geometries[ 0 ].index !== null; | |
| var attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) ); | |
| var morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) ); | |
| var attributes = {}; | |
| var morphAttributes = {}; | |
| var mergedGeometry = new BufferGeometry(); | |
| var offset = 0; | |
| for ( var i = 0; i < geometries.length; ++ i ) { | |
| var geometry = geometries[ i ]; | |
| // ensure that all geometries are indexed, or none | |
| if ( isIndexed !== ( geometry.index !== null ) ) return null; | |
| // gather attributes, exit early if they're different | |
| for ( var name in geometry.attributes ) { | |
| if ( ! attributesUsed.has( name ) ) return null; | |
| if ( attributes[ name ] === undefined ) attributes[ name ] = []; | |
| attributes[ name ].push( geometry.attributes[ name ] ); | |
| } | |
| // gather morph attributes, exit early if they're different | |
| for ( var name in geometry.morphAttributes ) { | |
| if ( ! morphAttributesUsed.has( name ) ) return null; | |
| if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = []; | |
| morphAttributes[ name ].push( geometry.morphAttributes[ name ] ); | |
| } | |
| // gather .userData | |
| mergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || []; | |
| mergedGeometry.userData.mergedUserData.push( geometry.userData ); | |
| if ( useGroups ) { | |
| var count; | |
| if ( isIndexed ) { | |
| count = geometry.index.count; | |
| } else if ( geometry.attributes.position !== undefined ) { | |
| count = geometry.attributes.position.count; | |
| } else { | |
| return null; | |
| } | |
| mergedGeometry.addGroup( offset, count, i ); | |
| offset += count; | |
| } | |
| } | |
| // merge indices | |
| if ( isIndexed ) { | |
| var indexOffset = 0; | |
| var mergedIndex = []; | |
| for ( var i = 0; i < geometries.length; ++ i ) { | |
| var index = geometries[ i ].index; | |
| for ( var j = 0; j < index.count; ++ j ) { | |
| mergedIndex.push( index.getX( j ) + indexOffset ); | |
| } | |
| indexOffset += geometries[ i ].attributes.position.count; | |
| } | |
| mergedGeometry.setIndex( mergedIndex ); | |
| } | |
| // merge attributes | |
| for ( var name in attributes ) { | |
| var mergedAttribute = this.mergeBufferAttributes( attributes[ name ] ); | |
| if ( ! mergedAttribute ) return null; | |
| mergedGeometry.addAttribute( name, mergedAttribute ); | |
| } | |
| // merge morph attributes | |
| for ( var name in morphAttributes ) { | |
| var numMorphTargets = morphAttributes[ name ][ 0 ].length; | |
| if ( numMorphTargets === 0 ) break; | |
| mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {}; | |
| mergedGeometry.morphAttributes[ name ] = []; | |
| for ( var i = 0; i < numMorphTargets; ++ i ) { | |
| var morphAttributesToMerge = []; | |
| for ( var j = 0; j < morphAttributes[ name ].length; ++ j ) { | |
| morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] ); | |
| } | |
| var mergedMorphAttribute = this.mergeBufferAttributes( morphAttributesToMerge ); | |
| if ( ! mergedMorphAttribute ) return null; | |
| mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute ); | |
| } | |
| } | |
| return mergedGeometry; | |
| }, | |
| /** | |
| * @param {Array<BufferAttribute>} attributes | |
| * @return {BufferAttribute} | |
| */ | |
| mergeBufferAttributes: function ( attributes ) { | |
| var TypedArray; | |
| var itemSize; | |
| var normalized; | |
| var arrayLength = 0; | |
| for ( var i = 0; i < attributes.length; ++ i ) { | |
| var attribute = attributes[ i ]; | |
| if ( attribute.isInterleavedBufferAttribute ) return null; | |
| if ( TypedArray === undefined ) TypedArray = attribute.array.constructor; | |
| if ( TypedArray !== attribute.array.constructor ) return null; | |
| if ( itemSize === undefined ) itemSize = attribute.itemSize; | |
| if ( itemSize !== attribute.itemSize ) return null; | |
| if ( normalized === undefined ) normalized = attribute.normalized; | |
| if ( normalized !== attribute.normalized ) return null; | |
| arrayLength += attribute.array.length; | |
| } | |
| var array = new TypedArray( arrayLength ); | |
| var offset = 0; | |
| for ( var i = 0; i < attributes.length; ++ i ) { | |
| array.set( attributes[ i ].array, offset ); | |
| offset += attributes[ i ].array.length; | |
| } | |
| return new BufferAttribute( array, itemSize, normalized ); | |
| }, | |
| /** | |
| * @param {Array<BufferAttribute>} attributes | |
| * @return {Array<InterleavedBufferAttribute>} | |
| */ | |
| interleaveAttributes: function ( attributes ) { | |
| // Interleaves the provided attributes into an InterleavedBuffer and returns | |
| // a set of InterleavedBufferAttributes for each attribute | |
| var TypedArray; | |
| var arrayLength = 0; | |
| var stride = 0; | |
| // calculate the the length and type of the interleavedBuffer | |
| for ( var i = 0, l = attributes.length; i < l; ++ i ) { | |
| var attribute = attributes[ i ]; | |
| if ( TypedArray === undefined ) TypedArray = attribute.array.constructor; | |
| if ( TypedArray !== attribute.array.constructor ) { | |
| console.warn( 'AttributeBuffers of different types cannot be interleaved' ); | |
| return null; | |
| } | |
| arrayLength += attribute.array.length; | |
| stride += attribute.itemSize; | |
| } | |
| // Create the set of buffer attributes | |
| var interleavedBuffer = new InterleavedBuffer( new TypedArray( arrayLength ), stride ); | |
| var offset = 0; | |
| var res = []; | |
| var getters = [ 'getX', 'getY', 'getZ', 'getW' ]; | |
| var setters = [ 'setX', 'setY', 'setZ', 'setW' ]; | |
| for ( var j = 0, l = attributes.length; j < l; j ++ ) { | |
| var attribute = attributes[ j ]; | |
| var itemSize = attribute.itemSize; | |
| var count = attribute.count; | |
| var iba = new InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized ); | |
| res.push( iba ); | |
| offset += itemSize; | |
| // Move the data for each attribute into the new interleavedBuffer | |
| // at the appropriate offset | |
| for ( var c = 0; c < count; c ++ ) { | |
| for ( var k = 0; k < itemSize; k ++ ) { | |
| iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) ); | |
| } | |
| } | |
| } | |
| return res; | |
| }, | |
| /** | |
| * @param {Array<BufferGeometry>} geometry | |
| * @return {number} | |
| */ | |
| estimateBytesUsed: function ( geometry ) { | |
| // Return the estimated memory used by this geometry in bytes | |
| // Calculate using itemSize, count, and BYTES_PER_ELEMENT to account | |
| // for InterleavedBufferAttributes. | |
| var mem = 0; | |
| for ( var name in geometry.attributes ) { | |
| var attr = geometry.getAttribute( name ); | |
| mem += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT; | |
| } | |
| var indices = geometry.getIndex(); | |
| mem += indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0; | |
| return mem; | |
| }, | |
| /** | |
| * @param {BufferGeometry} geometry | |
| * @param {number} tolerance | |
| * @return {BufferGeometry>} | |
| */ | |
| mergeVertices: function ( geometry, tolerance = 1e-4 ) { | |
| tolerance = Math.max( tolerance, Number.EPSILON ); | |
| // Generate an index buffer if the geometry doesn't have one, or optimize it | |
| // if it's already available. | |
| var hashToIndex = {}; | |
| var indices = geometry.getIndex(); | |
| var positions = geometry.getAttribute( 'position' ); | |
| var vertexCount = indices ? indices.count : positions.count; | |
| // next value for triangle indices | |
| var nextIndex = 0; | |
| // attributes and new attribute arrays | |
| var attributeNames = Object.keys( geometry.attributes ); | |
| var attrArrays = {}; | |
| var morphAttrsArrays = {}; | |
| var newIndices = []; | |
| var getters = [ 'getX', 'getY', 'getZ', 'getW' ]; | |
| // initialize the arrays | |
| for ( var i = 0, l = attributeNames.length; i < l; i ++ ) { | |
| var name = attributeNames[ i ]; | |
| attrArrays[ name ] = []; | |
| var morphAttr = geometry.morphAttributes[ name ]; | |
| if ( morphAttr ) { | |
| morphAttrsArrays[ name ] = new Array( morphAttr.length ).fill().map( () => [] ); | |
| } | |
| } | |
| // convert the error tolerance to an amount of decimal places to truncate to | |
| var decimalShift = Math.log10( 1 / tolerance ); | |
| var shiftMultiplier = Math.pow( 10, decimalShift ); | |
| for ( var i = 0; i < vertexCount; i ++ ) { | |
| var index = indices ? indices.getX( i ) : i; | |
| // Generate a hash for the vertex attributes at the current index 'i' | |
| var hash = ''; | |
| for ( var j = 0, l = attributeNames.length; j < l; j ++ ) { | |
| var name = attributeNames[ j ]; | |
| var attribute = geometry.getAttribute( name ); | |
| var itemSize = attribute.itemSize; | |
| for ( var k = 0; k < itemSize; k ++ ) { | |
| // double tilde truncates the decimal value | |
| hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * shiftMultiplier ) },`; | |
| } | |
| } | |
| // Add another reference to the vertex if it's already | |
| // used by another index | |
| if ( hash in hashToIndex ) { | |
| newIndices.push( hashToIndex[ hash ] ); | |
| } else { | |
| // copy data to the new index in the attribute arrays | |
| for ( var j = 0, l = attributeNames.length; j < l; j ++ ) { | |
| var name = attributeNames[ j ]; | |
| var attribute = geometry.getAttribute( name ); | |
| var morphAttr = geometry.morphAttributes[ name ]; | |
| var itemSize = attribute.itemSize; | |
| var newarray = attrArrays[ name ]; | |
| var newMorphArrays = morphAttrsArrays[ name ]; | |
| for ( var k = 0; k < itemSize; k ++ ) { | |
| var getterFunc = getters[ k ]; | |
| newarray.push( attribute[ getterFunc ]( index ) ); | |
| if ( morphAttr ) { | |
| for ( var m = 0, ml = morphAttr.length; m < ml; m ++ ) { | |
| newMorphArrays[ m ].push( morphAttr[ m ][ getterFunc ]( index ) ); | |
| } | |
| } | |
| } | |
| } | |
| hashToIndex[ hash ] = nextIndex; | |
| newIndices.push( nextIndex ); | |
| nextIndex ++; | |
| } | |
| } | |
| // Generate typed arrays from new attribute arrays and update | |
| // the attributeBuffers | |
| const result = geometry.clone(); | |
| for ( var i = 0, l = attributeNames.length; i < l; i ++ ) { | |
| var name = attributeNames[ i ]; | |
| var oldAttribute = geometry.getAttribute( name ); | |
| var attribute; | |
| var buffer = new oldAttribute.array.constructor( attrArrays[ name ] ); | |
| if ( oldAttribute.isInterleavedBufferAttribute ) { | |
| attribute = new BufferAttribute( buffer, oldAttribute.itemSize, oldAttribute.itemSize ); | |
| } else { | |
| attribute = geometry.getAttribute( name ).clone(); | |
| attribute.setArray( buffer ); | |
| } | |
| result.addAttribute( name, attribute ); | |
| // Update the attribute arrays | |
| if ( name in morphAttrsArrays ) { | |
| for ( var j = 0; j < morphAttrsArrays[ name ].length; j ++ ) { | |
| var morphAttribute = geometry.morphAttributes[ name ][ j ].clone(); | |
| morphAttribute.setArray( new morphAttribute.array.constructor( morphAttrsArrays[ name ][ j ] ) ); | |
| result.morphAttributes[ name ][ j ] = morphAttribute; | |
| } | |
| } | |
| } | |
| // Generate an index buffer typed array | |
| var cons = Uint8Array; | |
| if ( newIndices.length >= Math.pow( 2, 8 ) ) cons = Uint16Array; | |
| if ( newIndices.length >= Math.pow( 2, 16 ) ) cons = Uint32Array; | |
| var newIndexBuffer = new cons( newIndices ); | |
| var newIndices = null; | |
| if ( indices === null ) { | |
| newIndices = new BufferAttribute( newIndexBuffer, 1 ); | |
| } else { | |
| newIndices = geometry.getIndex().clone(); | |
| newIndices.setArray( newIndexBuffer ); | |
| } | |
| result.setIndex( newIndices ); | |
| return result; | |
| } | |
| }; | |
| export { BufferGeometryUtils }; | |

