256 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import Node, { addNodeClass } from '../core/Node.js';
 | |
| import { NodeUpdateType } from '../core/constants.js';
 | |
| import { float, nodeProxy, tslFn } from '../shadernode/ShaderNode.js';
 | |
| import { uniform } from '../core/UniformNode.js';
 | |
| import { reference } from './ReferenceNode.js';
 | |
| import { positionLocal } from './PositionNode.js';
 | |
| import { normalLocal } from './NormalNode.js';
 | |
| import { textureLoad } from './TextureNode.js';
 | |
| import { instanceIndex, vertexIndex } from '../core/IndexNode.js';
 | |
| import { ivec2, int } from '../shadernode/ShaderNode.js';
 | |
| import { DataArrayTexture, Vector2, Vector4, FloatType } from 'three';
 | |
| import { loop } from '../utils/LoopNode.js';
 | |
| 
 | |
| const morphTextures = new WeakMap();
 | |
| const morphVec4 = new Vector4();
 | |
| 
 | |
| const getMorph = tslFn( ( { bufferMap, influence, stride, width, depth, offset } ) => {
 | |
| 
 | |
| 	const texelIndex = int( vertexIndex ).mul( stride ).add( offset );
 | |
| 
 | |
| 	const y = texelIndex.div( width );
 | |
| 	const x = texelIndex.sub( y.mul( width ) );
 | |
| 
 | |
| 	const bufferAttrib = textureLoad( bufferMap, ivec2( x, y ) ).depth( depth );
 | |
| 
 | |
| 	return bufferAttrib.mul( influence );
 | |
| 
 | |
| } );
 | |
| 
 | |
| function getEntry( geometry ) {
 | |
| 
 | |
| 	const hasMorphPosition = geometry.morphAttributes.position !== undefined;
 | |
| 	const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
 | |
| 	const hasMorphColors = geometry.morphAttributes.color !== undefined;
 | |
| 
 | |
| 	// 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 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;
 | |
| 
 | |
| 		const maxTextureSize = 4096; // @TODO: Use 'capabilities.maxTextureSize'
 | |
| 
 | |
| 		if ( width > maxTextureSize ) {
 | |
| 
 | |
| 			height = Math.ceil( width / maxTextureSize );
 | |
| 			width = maxTextureSize;
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		const buffer = new Float32Array( width * height * 4 * morphTargetsCount );
 | |
| 
 | |
| 		const bufferTexture = new DataArrayTexture( buffer, width, height, morphTargetsCount );
 | |
| 		bufferTexture.type = FloatType;
 | |
| 		bufferTexture.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 ) {
 | |
| 
 | |
| 					morphVec4.fromBufferAttribute( morphTarget, j );
 | |
| 
 | |
| 					buffer[ offset + stride + 0 ] = morphVec4.x;
 | |
| 					buffer[ offset + stride + 1 ] = morphVec4.y;
 | |
| 					buffer[ offset + stride + 2 ] = morphVec4.z;
 | |
| 					buffer[ offset + stride + 3 ] = 0;
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				if ( hasMorphNormals === true ) {
 | |
| 
 | |
| 					morphVec4.fromBufferAttribute( morphNormal, j );
 | |
| 
 | |
| 					buffer[ offset + stride + 4 ] = morphVec4.x;
 | |
| 					buffer[ offset + stride + 5 ] = morphVec4.y;
 | |
| 					buffer[ offset + stride + 6 ] = morphVec4.z;
 | |
| 					buffer[ offset + stride + 7 ] = 0;
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				if ( hasMorphColors === true ) {
 | |
| 
 | |
| 					morphVec4.fromBufferAttribute( morphColor, j );
 | |
| 
 | |
| 					buffer[ offset + stride + 8 ] = morphVec4.x;
 | |
| 					buffer[ offset + stride + 9 ] = morphVec4.y;
 | |
| 					buffer[ offset + stride + 10 ] = morphVec4.z;
 | |
| 					buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morphVec4.w : 1;
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		entry = {
 | |
| 			count: morphTargetsCount,
 | |
| 			texture: bufferTexture,
 | |
| 			stride: vertexDataCount,
 | |
| 			size: new Vector2( width, height )
 | |
| 		};
 | |
| 
 | |
| 		morphTextures.set( geometry, entry );
 | |
| 
 | |
| 		function disposeTexture() {
 | |
| 
 | |
| 			bufferTexture.dispose();
 | |
| 
 | |
| 			morphTextures.delete( geometry );
 | |
| 
 | |
| 			geometry.removeEventListener( 'dispose', disposeTexture );
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		geometry.addEventListener( 'dispose', disposeTexture );
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	return entry;
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| class MorphNode extends Node {
 | |
| 
 | |
| 	constructor( mesh ) {
 | |
| 
 | |
| 		super( 'void' );
 | |
| 
 | |
| 		this.mesh = mesh;
 | |
| 		this.morphBaseInfluence = uniform( 1 );
 | |
| 
 | |
| 		this.updateType = NodeUpdateType.OBJECT;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	setup( builder ) {
 | |
| 
 | |
| 		const { geometry } = builder;
 | |
| 
 | |
| 		const hasMorphPosition = geometry.morphAttributes.position !== undefined;
 | |
| 		const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
 | |
| 
 | |
| 		const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
 | |
| 		const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
 | |
| 
 | |
| 		// nodes
 | |
| 
 | |
| 		const { texture: bufferMap, stride, size } = getEntry( geometry );
 | |
| 
 | |
| 		if ( hasMorphPosition === true ) positionLocal.mulAssign( this.morphBaseInfluence );
 | |
| 		if ( hasMorphNormals === true ) normalLocal.mulAssign( this.morphBaseInfluence );
 | |
| 
 | |
| 		const width = int( size.width );
 | |
| 
 | |
| 		loop( morphTargetsCount, ( { i } ) => {
 | |
| 
 | |
| 			const influence = float( 0 ).toVar();
 | |
| 
 | |
| 			if ( this.mesh.isInstancedMesh === true && ( this.mesh.morphTexture !== null && this.mesh.morphTexture !== undefined ) ) {
 | |
| 
 | |
| 				influence.assign( textureLoad( this.mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r );
 | |
| 
 | |
| 			} else {
 | |
| 
 | |
| 				influence.assign( reference( 'morphTargetInfluences', 'float' ).element( i ).toVar() );
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 			if ( hasMorphPosition === true ) {
 | |
| 
 | |
| 				positionLocal.addAssign( getMorph( {
 | |
| 					bufferMap,
 | |
| 					influence,
 | |
| 					stride,
 | |
| 					width,
 | |
| 					depth: i,
 | |
| 					offset: int( 0 )
 | |
| 				} ) );
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 			if ( hasMorphNormals === true ) {
 | |
| 
 | |
| 				normalLocal.addAssign( getMorph( {
 | |
| 					bufferMap,
 | |
| 					influence,
 | |
| 					stride,
 | |
| 					width,
 | |
| 					depth: i,
 | |
| 					offset: int( 1 )
 | |
| 				} ) );
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 		} );
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	update() {
 | |
| 
 | |
| 		const morphBaseInfluence = this.morphBaseInfluence;
 | |
| 
 | |
| 		if ( this.mesh.geometry.morphTargetsRelative ) {
 | |
| 
 | |
| 			morphBaseInfluence.value = 1;
 | |
| 
 | |
| 		} else {
 | |
| 
 | |
| 			morphBaseInfluence.value = 1 - this.mesh.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 );
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| export default MorphNode;
 | |
| 
 | |
| export const morphReference = nodeProxy( MorphNode );
 | |
| 
 | |
| addNodeClass( 'MorphNode', MorphNode );
 |