function numericalSort( a, b ) { return a[ 0 ] - b[ 0 ]; } function absNumericalSort( a, b ) { return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] ); } function WebGLMorphtargets( gl ) { const influencesList = {}; const morphInfluences = new Float32Array( 8 ); const workInfluences = []; for ( let i = 0; i < 8; i ++ ) { workInfluences[ i ] = [ i, 0 ]; } function update( object, geometry, material, program ) { const objectInfluences = object.morphTargetInfluences; // When object doesn't have morph target influences defined, we treat it as a 0-length array // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences const length = objectInfluences === undefined ? 0 : objectInfluences.length; let influences = influencesList[ geometry.id ]; if ( influences === undefined || influences.length !== length ) { // initialise list influences = []; for ( let i = 0; i < length; i ++ ) { influences[ i ] = [ i, 0 ]; } influencesList[ geometry.id ] = influences; } // Collect influences for ( let i = 0; i < length; i ++ ) { const influence = influences[ i ]; influence[ 0 ] = i; influence[ 1 ] = objectInfluences[ i ]; } influences.sort( absNumericalSort ); for ( let i = 0; i < 8; i ++ ) { if ( i < length && influences[ i ][ 1 ] ) { workInfluences[ i ][ 0 ] = influences[ i ][ 0 ]; workInfluences[ i ][ 1 ] = influences[ i ][ 1 ]; } else { workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER; workInfluences[ i ][ 1 ] = 0; } } workInfluences.sort( numericalSort ); const morphTargets = material.morphTargets && geometry.morphAttributes.position; const morphNormals = material.morphNormals && geometry.morphAttributes.normal; let morphInfluencesSum = 0; for ( let i = 0; i < 8; i ++ ) { const influence = workInfluences[ i ]; const index = influence[ 0 ]; const value = influence[ 1 ]; if ( index !== Number.MAX_SAFE_INTEGER && value ) { if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) { geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] ); } if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) { geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] ); } morphInfluences[ i ] = value; morphInfluencesSum += value; } else { if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) { geometry.deleteAttribute( 'morphTarget' + i ); } if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) { geometry.deleteAttribute( 'morphNormal' + i ); } morphInfluences[ i ] = 0; } } // GLSL shader uses formula baseinfluence * base + sum(target * influence) // This allows us to switch between absolute morphs and relative morphs without changing shader code // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence) const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences ); } return { update: update }; } export { WebGLMorphtargets };