download
raw
17.4 kB
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.