import { CubeReflectionMapping, CubeRefractionMapping, CubeUVReflectionMapping, CubeUVRefractionMapping, LinearEncoding, GammaEncoding } from '../../../../build/three.module.js'; import { NodeUniform } from './NodeUniform.js'; import { NodeUtils } from './NodeUtils.js'; import { NodeLib } from './NodeLib.js'; import { FunctionNode } from './FunctionNode.js'; import { ConstNode } from './ConstNode.js'; import { StructNode } from './StructNode.js'; import { Vector2Node } from '../inputs/Vector2Node.js'; import { Vector3Node } from '../inputs/Vector3Node.js'; import { Vector4Node } from '../inputs/Vector4Node.js'; import { TextureNode } from '../inputs/TextureNode.js'; import { CubeTextureNode } from '../inputs/CubeTextureNode.js'; import { TextureCubeNode } from '../misc/TextureCubeNode.js'; const elements = NodeUtils.elements, constructors = [ 'float', 'vec2', 'vec3', 'vec4' ], convertFormatToType = { float: 'f', vec2: 'v2', vec3: 'v3', vec4: 'v4', mat4: 'v4', int: 'i', bool: 'b' }, convertTypeToFormat = { t: 'sampler2D', tc: 'samplerCube', b: 'bool', i: 'int', f: 'float', c: 'vec3', v2: 'vec2', v3: 'vec3', v4: 'vec4', m3: 'mat3', m4: 'mat4' }; class NodeBuilder { constructor() { this.slots = []; this.caches = []; this.contexts = []; this.keywords = {}; this.nodeData = {}; this.requires = { uv: [], color: [], lights: false, fog: false, transparent: false, irradiance: false }; this.includes = { consts: [], functions: [], structs: [] }; this.attributes = {}; this.prefixCode = /* glsl */` #ifdef TEXTURE_LOD_EXT #define texCube(a, b) textureCube(a, b) #define texCubeBias(a, b, c) textureCubeLodEXT(a, b, c) #define tex2D(a, b) texture2D(a, b) #define tex2DBias(a, b, c) texture2DLodEXT(a, b, c) #else #define texCube(a, b) textureCube(a, b) #define texCubeBias(a, b, c) textureCube(a, b, c) #define tex2D(a, b) texture2D(a, b) #define tex2DBias(a, b, c) texture2D(a, b, c) #endif #include #include `; this.parsCode = { vertex: '', fragment: '' }; this.code = { vertex: '', fragment: '' }; this.nodeCode = { vertex: '', fragment: '' }; this.resultCode = { vertex: '', fragment: '' }; this.finalCode = { vertex: '', fragment: '' }; this.inputs = { uniforms: { list: [], vertex: [], fragment: [] }, vars: { varying: [], vertex: [], fragment: [] } }; // send to material this.defines = {}; this.uniforms = {}; this.extensions = {}; this.updaters = []; this.nodes = []; // -- this.analyzing = false; } build( vertex, fragment ) { this.buildShader( 'vertex', vertex ); this.buildShader( 'fragment', fragment ); for ( let i = 0; i < this.requires.uv.length; i ++ ) { if ( this.requires.uv[ i ] ) { const uvIndex = i > 0 ? i + 1 : ''; this.addVaryCode( 'varying vec2 vUv' + uvIndex + ';' ); if ( i > 0 ) { this.addVertexParsCode( 'attribute vec2 uv' + uvIndex + ';' ); } this.addVertexFinalCode( 'vUv' + uvIndex + ' = uv' + uvIndex + ';' ); } } if ( this.requires.color[ 0 ] ) { this.addVaryCode( 'varying vec4 vColor;' ); this.addVertexParsCode( 'attribute vec4 color;' ); this.addVertexFinalCode( 'vColor = color;' ); } if ( this.requires.color[ 1 ] ) { this.addVaryCode( 'varying vec4 vColor2;' ); this.addVertexParsCode( 'attribute vec4 color2;' ); this.addVertexFinalCode( 'vColor2 = color2;' ); } if ( this.requires.position ) { this.addVaryCode( 'varying vec3 vPosition;' ); this.addVertexFinalCode( 'vPosition = transformed;' ); } if ( this.requires.worldPosition ) { this.addVaryCode( 'varying vec3 vWPosition;' ); this.addVertexFinalCode( 'vWPosition = ( modelMatrix * vec4( transformed, 1.0 ) ).xyz;' ); } if ( this.requires.normal ) { this.addVaryCode( 'varying vec3 vObjectNormal;' ); this.addVertexFinalCode( 'vObjectNormal = normal;' ); } if ( this.requires.worldNormal ) { this.addVaryCode( 'varying vec3 vWNormal;' ); this.addVertexFinalCode( 'vWNormal = inverseTransformDirection( transformedNormal, viewMatrix ).xyz;' ); } return this; } buildShader( shader, node ) { this.resultCode[ shader ] = node.build( this.setShader( shader ), 'v4' ); } setMaterial( material, renderer ) { this.material = material; this.renderer = renderer; this.requires.lights = material.lights; this.requires.fog = material.fog; this.mergeDefines( material.defines ); return this; } addFlow( slot, cache, context ) { return this.addSlot( slot ).addCache( cache ).addContext( context ); } removeFlow() { return this.removeSlot().removeCache().removeContext(); } addCache( name ) { this.cache = name || ''; this.caches.push( this.cache ); return this; } removeCache() { this.caches.pop(); this.cache = this.caches[ this.caches.length - 1 ] || ''; return this; } addContext( context ) { this.context = Object.assign( {}, this.context, context ); this.context.extra = this.context.extra || {}; this.contexts.push( this.context ); return this; } removeContext() { this.contexts.pop(); this.context = this.contexts[ this.contexts.length - 1 ] || {}; return this; } addSlot( name = '' ) { this.slot = name; this.slots.push( this.slot ); return this; } removeSlot() { this.slots.pop(); this.slot = this.slots[ this.slots.length - 1 ] || ''; return this; } addVertexCode( code ) { this.addCode( code, 'vertex' ); } addFragmentCode( code ) { this.addCode( code, 'fragment' ); } addCode( code, shader ) { this.code[ shader || this.shader ] += code + '\n'; } addVertexNodeCode( code ) { this.addNodeCode( code, 'vertex' ); } addFragmentNodeCode( code ) { this.addNodeCode( code, 'fragment' ); } addNodeCode( code, shader ) { this.nodeCode[ shader || this.shader ] += code + '\n'; } clearNodeCode( shader ) { shader = shader || this.shader; const code = this.nodeCode[ shader ]; this.nodeCode[ shader ] = ''; return code; } clearVertexNodeCode( ) { return this.clearNodeCode( 'vertex' ); } clearFragmentNodeCode( ) { return this.clearNodeCode( 'fragment' ); } addVertexFinalCode( code ) { this.addFinalCode( code, 'vertex' ); } addFragmentFinalCode( code ) { this.addFinalCode( code, 'fragment' ); } addFinalCode( code, shader ) { this.finalCode[ shader || this.shader ] += code + '\n'; } addVertexParsCode( code ) { this.addParsCode( code, 'vertex' ); } addFragmentParsCode( code ) { this.addParsCode( code, 'fragment' ); } addParsCode( code, shader ) { this.parsCode[ shader || this.shader ] += code + '\n'; } addVaryCode( code ) { this.addVertexParsCode( code ); this.addFragmentParsCode( code ); } isCache( name ) { return this.caches.indexOf( name ) !== - 1; } isSlot( name ) { return this.slots.indexOf( name ) !== - 1; } define( name, value ) { this.defines[ name ] = value === undefined ? 1 : value; } require( name ) { this.requires[ name ] = true; } isDefined( name ) { return this.defines[ name ] !== undefined; } getVar( uuid, type, ns, shader = 'varying', prefix = 'V', label = '' ) { const vars = this.getVars( shader ); let data = vars[ uuid ]; if ( ! data ) { const index = vars.length, name = ns ? ns : 'node' + prefix + index + ( label ? '_' + label : '' ); data = { name: name, type: type }; vars.push( data ); vars[ uuid ] = data; } return data; } getTempVar( uuid, type, ns, label ) { return this.getVar( uuid, type, ns, this.shader, 'T', label ); } getAttribute( name, type ) { if ( ! this.attributes[ name ] ) { const varying = this.getVar( name, type ); this.addVertexParsCode( 'attribute ' + type + ' ' + name + ';' ); this.addVertexFinalCode( varying.name + ' = ' + name + ';' ); this.attributes[ name ] = { varying: varying, name: name, type: type }; } return this.attributes[ name ]; } getCode( shader ) { return [ this.prefixCode, this.parsCode[ shader ], this.getVarListCode( this.getVars( 'varying' ), 'varying' ), this.getVarListCode( this.inputs.uniforms[ shader ], 'uniform' ), this.getIncludesCode( 'consts', shader ), this.getIncludesCode( 'structs', shader ), this.getIncludesCode( 'functions', shader ), 'void main() {', this.getVarListCode( this.getVars( shader ) ), this.code[ shader ], this.resultCode[ shader ], this.finalCode[ shader ], '}' ].join( '\n' ); } getVarListCode( vars, prefix = '' ) { let code = ''; for ( let i = 0, l = vars.length; i < l; ++ i ) { const nVar = vars[ i ], type = nVar.type, name = nVar.name; const formatType = this.getFormatByType( type ); if ( formatType === undefined ) { throw new Error( 'Node pars ' + formatType + ' not found.' ); } code += prefix + ' ' + formatType + ' ' + name + ';\n'; } return code; } getVars( shader ) { return this.inputs.vars[ shader || this.shader ]; } getNodeData( node ) { const uuid = node.isNode ? node.uuid : node; return this.nodeData[ uuid ] = this.nodeData[ uuid ] || {}; } createUniform( shader, type, node, ns, needsUpdate, label ) { const uniforms = this.inputs.uniforms, index = uniforms.list.length; const uniform = new NodeUniform( { type: type, name: ns ? ns : 'nodeU' + index + ( label ? '_' + label : '' ), node: node, needsUpdate: needsUpdate } ); uniforms.list.push( uniform ); uniforms[ shader ].push( uniform ); uniforms[ shader ][ uniform.name ] = uniform; this.uniforms[ uniform.name ] = uniform; return uniform; } createVertexUniform( type, node, ns, needsUpdate, label ) { return this.createUniform( 'vertex', type, node, ns, needsUpdate, label ); } createFragmentUniform( type, node, ns, needsUpdate, label ) { return this.createUniform( 'fragment', type, node, ns, needsUpdate, label ); } include( node, parent, source ) { let includesStruct; node = typeof node === 'string' ? NodeLib.get( node ) : node; if ( this.context.include === false ) { return node.name; } if ( node instanceof FunctionNode ) { includesStruct = this.includes.functions; } else if ( node instanceof ConstNode ) { includesStruct = this.includes.consts; } else if ( node instanceof StructNode ) { includesStruct = this.includes.structs; } const includes = includesStruct[ this.shader ] = includesStruct[ this.shader ] || []; if ( node ) { let included = includes[ node.name ]; if ( ! included ) { included = includes[ node.name ] = { node: node, deps: [] }; includes.push( included ); included.src = node.build( this, 'source' ); } if ( node instanceof FunctionNode && parent && includes[ parent.name ] && includes[ parent.name ].deps.indexOf( node ) == - 1 ) { includes[ parent.name ].deps.push( node ); if ( node.includes && node.includes.length ) { let i = 0; do { this.include( node.includes[ i ++ ], parent ); } while ( i < node.includes.length ); } } if ( source ) { included.src = source; } return node.name; } else { throw new Error( 'Include not found.' ); } } colorToVectorProperties( color ) { return color.replace( 'r', 'x' ).replace( 'g', 'y' ).replace( 'b', 'z' ).replace( 'a', 'w' ); } colorToVector( color ) { return color.replace( /c/g, 'v3' ); } getIncludes( type, shader ) { return this.includes[ type ][ shader || this.shader ]; } getIncludesCode( type, shader ) { let includes = this.getIncludes( type, shader ); if ( ! includes ) return ''; let code = ''; includes = includes.sort( sortByPosition ); for ( let i = 0; i < includes.length; i ++ ) { if ( includes[ i ].src ) code += includes[ i ].src + '\n'; } return code; } getConstructorFromLength( len ) { return constructors[ len - 1 ]; } isTypeMatrix( format ) { return /^m/.test( format ); } getTypeLength( type ) { if ( type === 'f' ) return 1; return parseInt( this.colorToVector( type ).substr( 1 ) ); } getTypeFromLength( len ) { if ( len === 1 ) return 'f'; return 'v' + len; } findNode() { for ( let i = 0; i < arguments.length; i ++ ) { const nodeCandidate = arguments[ i ]; if ( nodeCandidate !== undefined && nodeCandidate.isNode ) { return nodeCandidate; } } } resolve() { for ( let i = 0; i < arguments.length; i ++ ) { const nodeCandidate = arguments[ i ]; if ( nodeCandidate !== undefined ) { if ( nodeCandidate.isNode ) { return nodeCandidate; } else if ( nodeCandidate.isTexture ) { switch ( nodeCandidate.mapping ) { case CubeReflectionMapping: case CubeRefractionMapping: return new CubeTextureNode( nodeCandidate ); break; case CubeUVReflectionMapping: case CubeUVRefractionMapping: return new TextureCubeNode( new TextureNode( nodeCandidate ) ); break; default: return new TextureNode( nodeCandidate ); } } else if ( nodeCandidate.isVector2 ) { return new Vector2Node( nodeCandidate ); } else if ( nodeCandidate.isVector3 ) { return new Vector3Node( nodeCandidate ); } else if ( nodeCandidate.isVector4 ) { return new Vector4Node( nodeCandidate ); } } } } format( code, from, to ) { const typeToType = this.colorToVector( to + ' <- ' + from ); switch ( typeToType ) { case 'f <- v2' : return code + '.x'; case 'f <- v3' : return code + '.x'; case 'f <- v4' : return code + '.x'; case 'f <- i' : case 'f <- b' : return 'float( ' + code + ' )'; case 'v2 <- f' : return 'vec2( ' + code + ' )'; case 'v2 <- v3': return code + '.xy'; case 'v2 <- v4': return code + '.xy'; case 'v2 <- i' : case 'v2 <- b' : return 'vec2( float( ' + code + ' ) )'; case 'v3 <- f' : return 'vec3( ' + code + ' )'; case 'v3 <- v2': return 'vec3( ' + code + ', 0.0 )'; case 'v3 <- v4': return code + '.xyz'; case 'v3 <- i' : case 'v3 <- b' : return 'vec2( float( ' + code + ' ) )'; case 'v4 <- f' : return 'vec4( ' + code + ' )'; case 'v4 <- v2': return 'vec4( ' + code + ', 0.0, 1.0 )'; case 'v4 <- v3': return 'vec4( ' + code + ', 1.0 )'; case 'v4 <- i' : case 'v4 <- b' : return 'vec4( float( ' + code + ' ) )'; case 'i <- f' : case 'i <- b' : return 'int( ' + code + ' )'; case 'i <- v2' : return 'int( ' + code + '.x )'; case 'i <- v3' : return 'int( ' + code + '.x )'; case 'i <- v4' : return 'int( ' + code + '.x )'; case 'b <- f' : return '( ' + code + ' != 0.0 )'; case 'b <- v2' : return '( ' + code + ' != vec2( 0.0 ) )'; case 'b <- v3' : return '( ' + code + ' != vec3( 0.0 ) )'; case 'b <- v4' : return '( ' + code + ' != vec4( 0.0 ) )'; case 'b <- i' : return '( ' + code + ' != 0 )'; } return code; } getTypeByFormat( format ) { return convertFormatToType[ format ] || format; } getFormatByType( type ) { return convertTypeToFormat[ type ] || type; } getUuid( uuid, useCache ) { useCache = useCache !== undefined ? useCache : true; if ( useCache && this.cache ) uuid = this.cache + '-' + uuid; return uuid; } getElementByIndex( index ) { return elements[ index ]; } getIndexByElement( elm ) { return elements.indexOf( elm ); } isShader( shader ) { return this.shader === shader; } setShader( shader ) { this.shader = shader; return this; } mergeDefines( defines ) { for ( const name in defines ) { this.defines[ name ] = defines[ name ]; } return this.defines; } mergeUniform( uniforms ) { for ( const name in uniforms ) { this.uniforms[ name ] = uniforms[ name ]; } return this.uniforms; } getTextureEncodingFromMap( map ) { let encoding; if ( ! map ) { encoding = LinearEncoding; } else if ( map.isTexture ) { encoding = map.encoding; } else if ( map.isWebGLRenderTarget ) { console.warn( 'THREE.WebGLPrograms.getTextureEncodingFromMap: don\'t use render targets as textures. Use their .texture property instead.' ); encoding = map.texture.encoding; } if ( encoding === LinearEncoding && this.context.gamma ) { encoding = GammaEncoding; } return encoding; } } function sortByPosition( a, b ) { return a.deps.length - b.deps.length; } export { NodeBuilder };