Buckets:
ktongue/docker_container / simsite /frontend /node_modules /three-mesh-bvh /src /utils /StaticGeometryGenerator.js
| import { BufferAttribute, BufferGeometry, Vector3, Vector4, Matrix4, Matrix3 } from 'three'; | |
| const _positionVector = /*@__PURE__*/ new Vector3(); | |
| const _normalVector = /*@__PURE__*/ new Vector3(); | |
| const _tangentVector = /*@__PURE__*/ new Vector3(); | |
| const _tangentVector4 = /*@__PURE__*/ new Vector4(); | |
| const _morphVector = /*@__PURE__*/ new Vector3(); | |
| const _temp = /*@__PURE__*/ new Vector3(); | |
| const _skinIndex = /*@__PURE__*/ new Vector4(); | |
| const _skinWeight = /*@__PURE__*/ new Vector4(); | |
| const _matrix = /*@__PURE__*/ new Matrix4(); | |
| const _boneMatrix = /*@__PURE__*/ new Matrix4(); | |
| // Confirms that the two provided attributes are compatible | |
| function validateAttributes( attr1, attr2 ) { | |
| if ( ! attr1 && ! attr2 ) { | |
| return; | |
| } | |
| const sameCount = attr1.count === attr2.count; | |
| const sameNormalized = attr1.normalized === attr2.normalized; | |
| const sameType = attr1.array.constructor === attr2.array.constructor; | |
| const sameItemSize = attr1.itemSize === attr2.itemSize; | |
| if ( ! sameCount || ! sameNormalized || ! sameType || ! sameItemSize ) { | |
| throw new Error(); | |
| } | |
| } | |
| // Clones the given attribute with a new compatible buffer attribute but no data | |
| function createAttributeClone( attr, countOverride = null ) { | |
| const cons = attr.array.constructor; | |
| const normalized = attr.normalized; | |
| const itemSize = attr.itemSize; | |
| const count = countOverride === null ? attr.count : countOverride; | |
| return new BufferAttribute( new cons( itemSize * count ), itemSize, normalized ); | |
| } | |
| // target offset is the number of elements in the target buffer stride to skip before copying the | |
| // attributes contents in to. | |
| function copyAttributeContents( attr, target, targetOffset = 0 ) { | |
| if ( attr.isInterleavedBufferAttribute ) { | |
| const itemSize = attr.itemSize; | |
| for ( let i = 0, l = attr.count; i < l; i ++ ) { | |
| const io = i + targetOffset; | |
| target.setX( io, attr.getX( i ) ); | |
| if ( itemSize >= 2 ) target.setY( io, attr.getY( i ) ); | |
| if ( itemSize >= 3 ) target.setZ( io, attr.getZ( i ) ); | |
| if ( itemSize >= 4 ) target.setW( io, attr.getW( i ) ); | |
| } | |
| } else { | |
| const array = target.array; | |
| const cons = array.constructor; | |
| const byteOffset = array.BYTES_PER_ELEMENT * attr.itemSize * targetOffset; | |
| const temp = new cons( array.buffer, byteOffset, attr.array.length ); | |
| temp.set( attr.array ); | |
| } | |
| } | |
| // Adds the "matrix" multiplied by "scale" to "target" | |
| function addScaledMatrix( target, matrix, scale ) { | |
| const targetArray = target.elements; | |
| const matrixArray = matrix.elements; | |
| for ( let i = 0, l = matrixArray.length; i < l; i ++ ) { | |
| targetArray[ i ] += matrixArray[ i ] * scale; | |
| } | |
| } | |
| // A version of "SkinnedMesh.boneTransform" for normals | |
| function boneNormalTransform( mesh, index, target ) { | |
| const skeleton = mesh.skeleton; | |
| const geometry = mesh.geometry; | |
| const bones = skeleton.bones; | |
| const boneInverses = skeleton.boneInverses; | |
| _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index ); | |
| _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index ); | |
| _matrix.elements.fill( 0 ); | |
| for ( let i = 0; i < 4; i ++ ) { | |
| const weight = _skinWeight.getComponent( i ); | |
| if ( weight !== 0 ) { | |
| const boneIndex = _skinIndex.getComponent( i ); | |
| _boneMatrix.multiplyMatrices( bones[ boneIndex ].matrixWorld, boneInverses[ boneIndex ] ); | |
| addScaledMatrix( _matrix, _boneMatrix, weight ); | |
| } | |
| } | |
| _matrix.multiply( mesh.bindMatrix ).premultiply( mesh.bindMatrixInverse ); | |
| target.transformDirection( _matrix ); | |
| return target; | |
| } | |
| // Applies the morph target data to the target vector | |
| function applyMorphTarget( morphData, morphInfluences, morphTargetsRelative, i, target ) { | |
| _morphVector.set( 0, 0, 0 ); | |
| for ( let j = 0, jl = morphData.length; j < jl; j ++ ) { | |
| const influence = morphInfluences[ j ]; | |
| const morphAttribute = morphData[ j ]; | |
| if ( influence === 0 ) continue; | |
| _temp.fromBufferAttribute( morphAttribute, i ); | |
| if ( morphTargetsRelative ) { | |
| _morphVector.addScaledVector( _temp, influence ); | |
| } else { | |
| _morphVector.addScaledVector( _temp.sub( target ), influence ); | |
| } | |
| } | |
| target.add( _morphVector ); | |
| } | |
| // Modified version of BufferGeometryUtils.mergeBufferGeometries that ignores morph targets and updates a attributes in place | |
| function mergeBufferGeometries( geometries, options = { useGroups: false, updateIndex: false, skipAttributes: [] }, targetGeometry = new BufferGeometry() ) { | |
| const isIndexed = geometries[ 0 ].index !== null; | |
| const { useGroups = false, updateIndex = false, skipAttributes = [] } = options; | |
| const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) ); | |
| const attributes = {}; | |
| let offset = 0; | |
| targetGeometry.clearGroups(); | |
| for ( let i = 0; i < geometries.length; ++ i ) { | |
| const geometry = geometries[ i ]; | |
| let attributesCount = 0; | |
| // ensure that all geometries are indexed, or none | |
| if ( isIndexed !== ( geometry.index !== null ) ) { | |
| throw new Error( 'StaticGeometryGenerator: All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' ); | |
| } | |
| // gather attributes, exit early if they're different | |
| for ( const name in geometry.attributes ) { | |
| if ( ! attributesUsed.has( name ) ) { | |
| throw new Error( 'StaticGeometryGenerator: All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' ); | |
| } | |
| if ( attributes[ name ] === undefined ) { | |
| attributes[ name ] = []; | |
| } | |
| attributes[ name ].push( geometry.attributes[ name ] ); | |
| attributesCount ++; | |
| } | |
| // ensure geometries have the same number of attributes | |
| if ( attributesCount !== attributesUsed.size ) { | |
| throw new Error( 'StaticGeometryGenerator: Make sure all geometries have the same number of attributes.' ); | |
| } | |
| if ( useGroups ) { | |
| let count; | |
| if ( isIndexed ) { | |
| count = geometry.index.count; | |
| } else if ( geometry.attributes.position !== undefined ) { | |
| count = geometry.attributes.position.count; | |
| } else { | |
| throw new Error( 'StaticGeometryGenerator: The geometry must have either an index or a position attribute' ); | |
| } | |
| targetGeometry.addGroup( offset, count, i ); | |
| offset += count; | |
| } | |
| } | |
| // merge indices | |
| if ( isIndexed ) { | |
| let forceUpdateIndex = false; | |
| if ( ! targetGeometry.index ) { | |
| let indexCount = 0; | |
| for ( let i = 0; i < geometries.length; ++ i ) { | |
| indexCount += geometries[ i ].index.count; | |
| } | |
| targetGeometry.setIndex( new BufferAttribute( new Uint32Array( indexCount ), 1, false ) ); | |
| forceUpdateIndex = true; | |
| } | |
| if ( updateIndex || forceUpdateIndex ) { | |
| const targetIndex = targetGeometry.index; | |
| let targetOffset = 0; | |
| let indexOffset = 0; | |
| for ( let i = 0; i < geometries.length; ++ i ) { | |
| const geometry = geometries[ i ]; | |
| const index = geometry.index; | |
| if ( skipAttributes[ i ] !== true ) { | |
| for ( let j = 0; j < index.count; ++ j ) { | |
| targetIndex.setX( targetOffset, index.getX( j ) + indexOffset ); | |
| targetOffset ++; | |
| } | |
| } | |
| indexOffset += geometry.attributes.position.count; | |
| } | |
| } | |
| } | |
| // merge attributes | |
| for ( const name in attributes ) { | |
| const attrList = attributes[ name ]; | |
| if ( ! ( name in targetGeometry.attributes ) ) { | |
| let count = 0; | |
| for ( const key in attrList ) { | |
| count += attrList[ key ].count; | |
| } | |
| targetGeometry.setAttribute( name, createAttributeClone( attributes[ name ][ 0 ], count ) ); | |
| } | |
| const targetAttribute = targetGeometry.attributes[ name ]; | |
| let offset = 0; | |
| for ( let i = 0, l = attrList.length; i < l; i ++ ) { | |
| const attr = attrList[ i ]; | |
| if ( skipAttributes[ i ] !== true ) { | |
| copyAttributeContents( attr, targetAttribute, offset ); | |
| } | |
| offset += attr.count; | |
| } | |
| } | |
| return targetGeometry; | |
| } | |
| function checkTypedArrayEquality( a, b ) { | |
| if ( a === null || b === null ) { | |
| return a === b; | |
| } | |
| if ( a.length !== b.length ) { | |
| return false; | |
| } | |
| for ( let i = 0, l = a.length; i < l; i ++ ) { | |
| if ( a[ i ] !== b[ i ] ) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| function invertGeometry( geometry ) { | |
| const { index, attributes } = geometry; | |
| if ( index ) { | |
| for ( let i = 0, l = index.count; i < l; i += 3 ) { | |
| const v0 = index.getX( i ); | |
| const v2 = index.getX( i + 2 ); | |
| index.setX( i, v2 ); | |
| index.setX( i + 2, v0 ); | |
| } | |
| } else { | |
| for ( const key in attributes ) { | |
| const attr = attributes[ key ]; | |
| const itemSize = attr.itemSize; | |
| for ( let i = 0, l = attr.count; i < l; i += 3 ) { | |
| for ( let j = 0; j < itemSize; j ++ ) { | |
| const v0 = attr.getComponent( i, j ); | |
| const v2 = attr.getComponent( i + 2, j ); | |
| attr.setComponent( i, j, v2 ); | |
| attr.setComponent( i + 2, j, v0 ); | |
| } | |
| } | |
| } | |
| } | |
| return geometry; | |
| } | |
| // Checks whether the geometry changed between this and last evaluation | |
| class GeometryDiff { | |
| constructor( mesh ) { | |
| this.matrixWorld = new Matrix4(); | |
| this.geometryHash = null; | |
| this.boneMatrices = null; | |
| this.primitiveCount = - 1; | |
| this.mesh = mesh; | |
| this.update(); | |
| } | |
| update() { | |
| const mesh = this.mesh; | |
| const geometry = mesh.geometry; | |
| const skeleton = mesh.skeleton; | |
| const primitiveCount = ( geometry.index ? geometry.index.count : geometry.attributes.position.count ) / 3; | |
| this.matrixWorld.copy( mesh.matrixWorld ); | |
| this.geometryHash = geometry.attributes.position.version; | |
| this.primitiveCount = primitiveCount; | |
| if ( skeleton ) { | |
| // ensure the bone matrix array is updated to the appropriate length | |
| if ( ! skeleton.boneTexture ) { | |
| skeleton.computeBoneTexture(); | |
| } | |
| skeleton.update(); | |
| // copy data if possible otherwise clone it | |
| const boneMatrices = skeleton.boneMatrices; | |
| if ( ! this.boneMatrices || this.boneMatrices.length !== boneMatrices.length ) { | |
| this.boneMatrices = boneMatrices.slice(); | |
| } else { | |
| this.boneMatrices.set( boneMatrices ); | |
| } | |
| } else { | |
| this.boneMatrices = null; | |
| } | |
| } | |
| didChange() { | |
| const mesh = this.mesh; | |
| const geometry = mesh.geometry; | |
| const primitiveCount = ( geometry.index ? geometry.index.count : geometry.attributes.position.count ) / 3; | |
| const identical = | |
| this.matrixWorld.equals( mesh.matrixWorld ) && | |
| this.geometryHash === geometry.attributes.position.version && | |
| checkTypedArrayEquality( mesh.skeleton && mesh.skeleton.boneMatrices || null, this.boneMatrices ) && | |
| this.primitiveCount === primitiveCount; | |
| return ! identical; | |
| } | |
| } | |
| export class StaticGeometryGenerator { | |
| constructor( meshes ) { | |
| if ( ! Array.isArray( meshes ) ) { | |
| meshes = [ meshes ]; | |
| } | |
| const finalMeshes = []; | |
| meshes.forEach( object => { | |
| object.traverseVisible( c => { | |
| if ( c.isMesh ) { | |
| finalMeshes.push( c ); | |
| } | |
| } ); | |
| } ); | |
| this.meshes = finalMeshes; | |
| this.useGroups = true; | |
| this.applyWorldTransforms = true; | |
| this.attributes = [ 'position', 'normal', 'color', 'tangent', 'uv', 'uv2' ]; | |
| this._intermediateGeometry = new Array( finalMeshes.length ).fill().map( () => new BufferGeometry() ); | |
| this._diffMap = new WeakMap(); | |
| } | |
| getMaterials() { | |
| const materials = []; | |
| this.meshes.forEach( mesh => { | |
| if ( Array.isArray( mesh.material ) ) { | |
| materials.push( ...mesh.material ); | |
| } else { | |
| materials.push( mesh.material ); | |
| } | |
| } ); | |
| return materials; | |
| } | |
| generate( targetGeometry = new BufferGeometry() ) { | |
| // track which attributes have been updated and which to skip to avoid unnecessary attribute copies | |
| let skipAttributes = []; | |
| const { meshes, useGroups, _intermediateGeometry, _diffMap } = this; | |
| for ( let i = 0, l = meshes.length; i < l; i ++ ) { | |
| const mesh = meshes[ i ]; | |
| const geom = _intermediateGeometry[ i ]; | |
| const diff = _diffMap.get( mesh ); | |
| if ( ! diff || diff.didChange( mesh ) ) { | |
| this._convertToStaticGeometry( mesh, geom ); | |
| skipAttributes.push( false ); | |
| if ( ! diff ) { | |
| _diffMap.set( mesh, new GeometryDiff( mesh ) ); | |
| } else { | |
| diff.update(); | |
| } | |
| } else { | |
| skipAttributes.push( true ); | |
| } | |
| } | |
| if ( _intermediateGeometry.length === 0 ) { | |
| // if there are no geometries then just create a fake empty geometry to provide | |
| targetGeometry.setIndex( null ); | |
| // remove all geometry | |
| const attrs = targetGeometry.attributes; | |
| for ( const key in attrs ) { | |
| targetGeometry.deleteAttribute( key ); | |
| } | |
| // create dummy attributes | |
| for ( const key in this.attributes ) { | |
| targetGeometry.setAttribute( this.attributes[ key ], new BufferAttribute( new Float32Array( 0 ), 4, false ) ); | |
| } | |
| } else { | |
| mergeBufferGeometries( _intermediateGeometry, { useGroups, skipAttributes }, targetGeometry ); | |
| } | |
| for ( const key in targetGeometry.attributes ) { | |
| targetGeometry.attributes[ key ].needsUpdate = true; | |
| } | |
| return targetGeometry; | |
| } | |
| _convertToStaticGeometry( mesh, targetGeometry = new BufferGeometry() ) { | |
| const geometry = mesh.geometry; | |
| const applyWorldTransforms = this.applyWorldTransforms; | |
| const includeNormal = this.attributes.includes( 'normal' ); | |
| const includeTangent = this.attributes.includes( 'tangent' ); | |
| const attributes = geometry.attributes; | |
| const targetAttributes = targetGeometry.attributes; | |
| // initialize the attributes if they don't exist | |
| if ( ! targetGeometry.index && geometry.index ) { | |
| targetGeometry.index = geometry.index.clone(); | |
| } | |
| if ( ! targetAttributes.position ) { | |
| targetGeometry.setAttribute( 'position', createAttributeClone( attributes.position ) ); | |
| } | |
| if ( includeNormal && ! targetAttributes.normal && attributes.normal ) { | |
| targetGeometry.setAttribute( 'normal', createAttributeClone( attributes.normal ) ); | |
| } | |
| if ( includeTangent && ! targetAttributes.tangent && attributes.tangent ) { | |
| targetGeometry.setAttribute( 'tangent', createAttributeClone( attributes.tangent ) ); | |
| } | |
| // ensure the attributes are consistent | |
| validateAttributes( geometry.index, targetGeometry.index ); | |
| validateAttributes( attributes.position, targetAttributes.position ); | |
| if ( includeNormal ) { | |
| validateAttributes( attributes.normal, targetAttributes.normal ); | |
| } | |
| if ( includeTangent ) { | |
| validateAttributes( attributes.tangent, targetAttributes.tangent ); | |
| } | |
| // generate transformed vertex attribute data | |
| const position = attributes.position; | |
| const normal = includeNormal ? attributes.normal : null; | |
| const tangent = includeTangent ? attributes.tangent : null; | |
| const morphPosition = geometry.morphAttributes.position; | |
| const morphNormal = geometry.morphAttributes.normal; | |
| const morphTangent = geometry.morphAttributes.tangent; | |
| const morphTargetsRelative = geometry.morphTargetsRelative; | |
| const morphInfluences = mesh.morphTargetInfluences; | |
| const normalMatrix = new Matrix3(); | |
| normalMatrix.getNormalMatrix( mesh.matrixWorld ); | |
| // copy the index | |
| if ( geometry.index ) { | |
| targetGeometry.index.array.set( geometry.index.array ); | |
| } | |
| // copy and apply other attributes | |
| for ( let i = 0, l = attributes.position.count; i < l; i ++ ) { | |
| _positionVector.fromBufferAttribute( position, i ); | |
| if ( normal ) { | |
| _normalVector.fromBufferAttribute( normal, i ); | |
| } | |
| if ( tangent ) { | |
| _tangentVector4.fromBufferAttribute( tangent, i ); | |
| _tangentVector.fromBufferAttribute( tangent, i ); | |
| } | |
| // apply morph target transform | |
| if ( morphInfluences ) { | |
| if ( morphPosition ) { | |
| applyMorphTarget( morphPosition, morphInfluences, morphTargetsRelative, i, _positionVector ); | |
| } | |
| if ( morphNormal ) { | |
| applyMorphTarget( morphNormal, morphInfluences, morphTargetsRelative, i, _normalVector ); | |
| } | |
| if ( morphTangent ) { | |
| applyMorphTarget( morphTangent, morphInfluences, morphTargetsRelative, i, _tangentVector ); | |
| } | |
| } | |
| // apply bone transform | |
| if ( mesh.isSkinnedMesh ) { | |
| mesh.applyBoneTransform( i, _positionVector ); | |
| if ( normal ) { | |
| boneNormalTransform( mesh, i, _normalVector ); | |
| } | |
| if ( tangent ) { | |
| boneNormalTransform( mesh, i, _tangentVector ); | |
| } | |
| } | |
| // update the vectors of the attributes | |
| if ( applyWorldTransforms ) { | |
| _positionVector.applyMatrix4( mesh.matrixWorld ); | |
| } | |
| targetAttributes.position.setXYZ( i, _positionVector.x, _positionVector.y, _positionVector.z ); | |
| if ( normal ) { | |
| if ( applyWorldTransforms ) { | |
| _normalVector.applyNormalMatrix( normalMatrix ); | |
| } | |
| targetAttributes.normal.setXYZ( i, _normalVector.x, _normalVector.y, _normalVector.z ); | |
| } | |
| if ( tangent ) { | |
| if ( applyWorldTransforms ) { | |
| _tangentVector.transformDirection( mesh.matrixWorld ); | |
| } | |
| targetAttributes.tangent.setXYZW( i, _tangentVector.x, _tangentVector.y, _tangentVector.z, _tangentVector4.w ); | |
| } | |
| } | |
| // copy other attributes over | |
| for ( const i in this.attributes ) { | |
| const key = this.attributes[ i ]; | |
| if ( key === 'position' || key === 'tangent' || key === 'normal' || ! ( key in attributes ) ) { | |
| continue; | |
| } | |
| if ( ! targetAttributes[ key ] ) { | |
| targetGeometry.setAttribute( key, createAttributeClone( attributes[ key ] ) ); | |
| } | |
| validateAttributes( attributes[ key ], targetAttributes[ key ] ); | |
| copyAttributeContents( attributes[ key ], targetAttributes[ key ] ); | |
| } | |
| if ( mesh.matrixWorld.determinant() < 0 ) { | |
| invertGeometry( targetGeometry ); | |
| } | |
| return targetGeometry; | |
| } | |
| } | |
Xet Storage Details
- Size:
- 17.4 kB
- Xet hash:
- 0ff4520104275eab3984062d35f5df3ca4cd088f9482e54d89da2231ba930428
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.