|
@@ -1,10 +1,157 @@
|
|
|
import Node, { addNodeClass } from '../core/Node.js';
|
|
|
import { NodeUpdateType } from '../core/constants.js';
|
|
|
-import { nodeProxy } from '../shadernode/ShaderNode.js';
|
|
|
+import { nodeProxy, tslFn } from '../shadernode/ShaderNode.js';
|
|
|
import { uniform } from '../core/UniformNode.js';
|
|
|
-import { reference } from './ReferenceNode.js';
|
|
|
-import { bufferAttribute } from './BufferAttributeNode.js';
|
|
|
+import { referenceIndex } from './ReferenceNode.js';
|
|
|
import { positionLocal } from './PositionNode.js';
|
|
|
+import { normalLocal } from './NormalNode.js';
|
|
|
+import { textureLoad } from './TextureNode.js';
|
|
|
+import { vertexIndex } from '../core/IndexNode.js';
|
|
|
+import { ivec2, int } from '../shadernode/ShaderNode.js';
|
|
|
+import { DataArrayTexture, Vector2, Vector4, FloatType } from 'three';
|
|
|
+
|
|
|
+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 {
|
|
|
|
|
@@ -19,29 +166,57 @@ class MorphNode extends Node {
|
|
|
|
|
|
}
|
|
|
|
|
|
- setupAttribute( name, assignNode = positionLocal ) {
|
|
|
+ setup( builder ) {
|
|
|
|
|
|
- const mesh = this.mesh;
|
|
|
- const attributes = mesh.geometry.morphAttributes[ name ];
|
|
|
+ const { geometry } = builder;
|
|
|
|
|
|
- assignNode.mulAssign( this.morphBaseInfluence );
|
|
|
+ const hasMorphPosition = geometry.morphAttributes.position !== undefined;
|
|
|
+ const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
|
|
|
|
|
|
- for ( let i = 0; i < attributes.length; i ++ ) {
|
|
|
+ const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
|
|
|
+ const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
|
|
|
|
|
|
- const attribute = attributes[ i ];
|
|
|
+ // nodes
|
|
|
|
|
|
- const bufferAttrib = bufferAttribute( attribute.array, 'vec3' );
|
|
|
- const influence = reference( i, 'float', mesh.morphTargetInfluences );
|
|
|
+ const { texture: bufferMap, stride, size } = getEntry( geometry );
|
|
|
|
|
|
- assignNode.addAssign( bufferAttrib.mul( influence ) );
|
|
|
+ if ( hasMorphPosition === true ) positionLocal.mulAssign( this.morphBaseInfluence );
|
|
|
+ if ( hasMorphNormals === true ) normalLocal.mulAssign( this.morphBaseInfluence );
|
|
|
|
|
|
- }
|
|
|
+ const width = int( size.width );
|
|
|
|
|
|
- }
|
|
|
+ for ( let i = 0; i < morphTargetsCount; i ++ ) {
|
|
|
+
|
|
|
+ const influence = referenceIndex( 'morphTargetInfluences', i, 'float' );
|
|
|
+ const depth = int( i );
|
|
|
+
|
|
|
+ if ( hasMorphPosition === true ) {
|
|
|
+
|
|
|
+ positionLocal.addAssign( getMorph( {
|
|
|
+ bufferMap,
|
|
|
+ influence,
|
|
|
+ stride,
|
|
|
+ width,
|
|
|
+ depth,
|
|
|
+ offset: int( 0 )
|
|
|
+ } ) );
|
|
|
|
|
|
- setup( /*builder*/ ) {
|
|
|
+ }
|
|
|
|
|
|
- this.setupAttribute( 'position' );
|
|
|
+ if ( hasMorphNormals === true ) {
|
|
|
+
|
|
|
+ normalLocal.addAssign( getMorph( {
|
|
|
+ bufferMap,
|
|
|
+ influence,
|
|
|
+ stride,
|
|
|
+ width,
|
|
|
+ depth,
|
|
|
+ offset: int( 1 )
|
|
|
+ } ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|