three
Version:
JavaScript 3D library
301 lines (173 loc) • 7.75 kB
JavaScript
import { FloatType } from '../../constants.js';
import { DataArrayTexture } from '../../textures/DataArrayTexture.js';
import { Vector4 } from '../../math/Vector4.js';
import { Vector2 } from '../../math/Vector2.js';
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, capabilities, textures ) {
const influencesList = {};
const morphInfluences = new Float32Array( 8 );
const morphTextures = new WeakMap();
const morph = new Vector4();
const workInfluences = [];
for ( let i = 0; i < 8; i ++ ) {
workInfluences[ i ] = [ i, 0 ];
}
function update( object, geometry, program ) {
const objectInfluences = object.morphTargetInfluences;
if ( capabilities.isWebGL2 === true ) {
// instead of using attributes, the WebGL 2 code path encodes morph targets
// into an array of data textures. Each layer represents a single morph target.
const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
let entry = morphTextures.get( geometry );
if ( entry === undefined || entry.count !== morphTargetsCount ) {
if ( entry !== undefined ) entry.texture.dispose();
const hasMorphPosition = geometry.morphAttributes.position !== undefined;
const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
const hasMorphColors = geometry.morphAttributes.color !== undefined;
const morphTargets = geometry.morphAttributes.position || [];
const morphNormals = geometry.morphAttributes.normal || [];
const morphColors = geometry.morphAttributes.color || [];
let vertexDataCount = 0;
if ( hasMorphPosition === true ) vertexDataCount = 1;
if ( hasMorphNormals === true ) vertexDataCount = 2;
if ( hasMorphColors === true ) vertexDataCount = 3;
let width = geometry.attributes.position.count * vertexDataCount;
let height = 1;
if ( width > capabilities.maxTextureSize ) {
height = Math.ceil( width / capabilities.maxTextureSize );
width = capabilities.maxTextureSize;
}
const buffer = new Float32Array( width * height * 4 * morphTargetsCount );
const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount );
texture.type = FloatType;
texture.needsUpdate = true;
// fill buffer
const vertexDataStride = vertexDataCount * 4;
for ( let i = 0; i < morphTargetsCount; i ++ ) {
const morphTarget = morphTargets[ i ];
const morphNormal = morphNormals[ i ];
const morphColor = morphColors[ i ];
const offset = width * height * 4 * i;
for ( let j = 0; j < morphTarget.count; j ++ ) {
const stride = j * vertexDataStride;
if ( hasMorphPosition === true ) {
morph.fromBufferAttribute( morphTarget, j );
buffer[ offset + stride + 0 ] = morph.x;
buffer[ offset + stride + 1 ] = morph.y;
buffer[ offset + stride + 2 ] = morph.z;
buffer[ offset + stride + 3 ] = 0;
}
if ( hasMorphNormals === true ) {
morph.fromBufferAttribute( morphNormal, j );
buffer[ offset + stride + 4 ] = morph.x;
buffer[ offset + stride + 5 ] = morph.y;
buffer[ offset + stride + 6 ] = morph.z;
buffer[ offset + stride + 7 ] = 0;
}
if ( hasMorphColors === true ) {
morph.fromBufferAttribute( morphColor, j );
buffer[ offset + stride + 8 ] = morph.x;
buffer[ offset + stride + 9 ] = morph.y;
buffer[ offset + stride + 10 ] = morph.z;
buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1;
}
}
}
entry = {
count: morphTargetsCount,
texture: texture,
size: new Vector2( width, height )
};
morphTextures.set( geometry, entry );
function disposeTexture() {
texture.dispose();
morphTextures.delete( geometry );
geometry.removeEventListener( 'dispose', disposeTexture );
}
geometry.addEventListener( 'dispose', disposeTexture );
}
//
let morphInfluencesSum = 0;
for ( let i = 0; i < objectInfluences.length; i ++ ) {
morphInfluencesSum += objectInfluences[ i ];
}
const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences );
program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures );
program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size );
} else {
// 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 = geometry.morphAttributes.position;
const 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 };