Skip to content

⬅️ Back to Table of Contents

📄 NodeBuilder.js

📊 Analysis Summary

Metric Count
🔧 Functions 126
🧱 Classes 1
📦 Imports 41
📊 Variables & Constants 84

📚 Table of Contents

🛠️ File Location:

📂 src/nodes/core/NodeBuilder.js

📦 Imports

Name Source
NodeUniform ./NodeUniform.js
NodeAttribute ./NodeAttribute.js
NodeVarying ./NodeVarying.js
NodeVar ./NodeVar.js
NodeCode ./NodeCode.js
NodeCache ./NodeCache.js
ParameterNode ./ParameterNode.js
StructType ./StructType.js
FunctionNode ../code/FunctionNode.js
NodeMaterial ../../materials/nodes/NodeMaterial.js
getTypeFromLength ./NodeUtils.js
NodeUpdateType ./constants.js
defaultBuildStages ./constants.js
shaderStages ./constants.js
NumberNodeUniform ../../renderers/common/nodes/NodeUniform.js
Vector2NodeUniform ../../renderers/common/nodes/NodeUniform.js
Vector3NodeUniform ../../renderers/common/nodes/NodeUniform.js
Vector4NodeUniform ../../renderers/common/nodes/NodeUniform.js
ColorNodeUniform ../../renderers/common/nodes/NodeUniform.js
Matrix2NodeUniform ../../renderers/common/nodes/NodeUniform.js
Matrix3NodeUniform ../../renderers/common/nodes/NodeUniform.js
Matrix4NodeUniform ../../renderers/common/nodes/NodeUniform.js
stack ./StackNode.js
getCurrentStack ../tsl/TSLBase.js
setCurrentStack ../tsl/TSLBase.js
CubeRenderTarget ../../renderers/common/CubeRenderTarget.js
ChainMap ../../renderers/common/ChainMap.js
BindGroup ../../renderers/common/BindGroup.js
REVISION ../../constants.js
IntType ../../constants.js
UnsignedIntType ../../constants.js
LinearFilter ../../constants.js
LinearMipmapNearestFilter ../../constants.js
NearestMipmapLinearFilter ../../constants.js
LinearMipmapLinearFilter ../../constants.js
RenderTarget ../../core/RenderTarget.js
Color ../../math/Color.js
Vector2 ../../math/Vector2.js
Vector3 ../../math/Vector3.js
Vector4 ../../math/Vector4.js
Float16BufferAttribute ../../core/BufferAttribute.js

Variables & Constants

Name Type Kind Value Exported
rendererCache WeakMap<WeakKey, any> let/var new WeakMap()
typeFromArray Map<Int8ArrayConstructor, string> let/var new Map( [ [ Int8Array, 'int' ], [ Int16Array, 'int' ], [ Int32Array, 'int' ]...
bindingsArray any[] let/var []
sharedGroup boolean let/var true
bindGroup any let/var *not shown*
bindings any let/var this.bindings[ shaderStage ]
bindGroup any let/var bindings[ groupName ]
bindingsGroups BindGroup[] let/var this.bindGroups
groups {} let/var {}
bindings any let/var this.bindings
uniforms any let/var bindings[ shaderStage ][ groupName ]
groupUniforms any let/var groups[ groupName ] \|\| ( groups[ groupName ] = [] )
group any let/var groups[ groupName ]
bindingGroup BindGroup let/var bindingsGroups[ i ]
context any let/var { ...this.context }
snippet string let/var this.generateArrayDeclaration( type, count ) + '( '
value Node let/var values ? values[ i ] : null
snippets any[] let/var []
attributes NodeAttribute[] let/var this.attributes
attribute NodeAttribute let/var new NodeAttribute( name, type )
type any let/var texture.type
prefix string let/var componentType === 'float' ? '' : componentType[ 0 ]
dataAttribute BufferAttribute let/var attribute
array any let/var dataAttribute.array
itemSize any let/var attribute.itemSize
normalized any let/var attribute.normalized
arrayType any let/var *not shown*
lastStack StackNode let/var this.stack
data any let/var nodeData[ shaderStage ]
subBuilds any let/var nodeData.any ? nodeData.any.subBuilds : null
bufferAttribute any let/var nodeData.bufferAttribute
index number let/var this.uniforms.index ++
structType any let/var nodeData.structType
index number let/var this.structs.index ++
nodeUniform any let/var nodeData.uniform
index number let/var this.uniforms.index ++
nodeVar any let/var nodeData[ subBuildVariable ]
idNS "_const" \| "_var" let/var readOnly ? '_const' : '_var'
vars number \| NodeVar[] let/var this.vars[ shaderStage ] \|\| ( this.vars[ shaderStage ] = [] )
id number \| NodeVar[] let/var this.vars[ idNS ] \|\| ( this.vars[ idNS ] = 0 )
nodeVarying any let/var nodeData[ subBuildVarying ]
varyings NodeVarying[] let/var this.varyings
index number let/var varyings.length
shaderStage "compute" \| "vertex" \| "fragment" \... let/var this.shaderStage
declarations any let/var this.declarations[ shaderStage ] \|\| ( this.declarations[ shaderStage ] = {} )
index number let/var 1
name string let/var property
nodeCode any let/var nodeData.code
codes NodeCode[] let/var this.codes[ shaderStage ] \|\| ( this.codes[ shaderStage ] = [] )
index number let/var codes.length
needsFlowCode boolean let/var true
nodeBlockHierarchy Node let/var nodeBlock
flowCodes any let/var nodeData.flowCodes \|\| ( nodeData.flowCodes = [] )
codeBlock any let/var nodeData.flowCodeBlock \|\| ( nodeData.flowCodeBlock = new WeakMap() )
fn FunctionNode let/var new FunctionNode()
previous FunctionNode let/var this.currentFunctionNode
layout any let/var shaderNode.layout
index number let/var 0
inputs { [Symbol.iterator](): { next: () => ... let/var { [ Symbol.iterator ]() { let index = 0; const values = Object.values( this )...
previousFlow { code: string; } let/var this.flow
previousVars { [x: string]: number \| NodeVar[]; } let/var this.vars
previousDeclarations any let/var this.declarations
previousCache NodeCache let/var this.cache
previousBuildStage "setup" \| "generate" \| "analyze" let/var this.buildStage
previousStack StackNode let/var this.stack
flow { code: string; } let/var { code: '' }
previousFlow { code: string; } let/var this.flow
flow { code: string; } let/var { code: '' }
previousTab string let/var this.tab
previousCache NodeCache let/var this.cache
previousShaderStage "compute" \| "vertex" \| "fragment" \... let/var this.shaderStage
previousContext any let/var this.context
context any let/var { ...this.context }
result any let/var null
snippet string let/var ''
vars number \| NodeVar[] let/var this.vars[ shaderStage ]
codes NodeCode[] let/var this.codes[ shaderStage ]
code string let/var ''
subBuilds any let/var *not shown*
subBuildLayers SubBuildNode[] let/var this.subBuildLayers
subBuild any let/var subBuilds[ i ]
subBuild any let/var *not shown*
result any let/var *not shown*
flowNodes Node[] let/var this.flowNodes[ shaderStage ]

Functions

toFloat(value: any): string

Parameters:

  • value any

Returns: string

Calls:

  • /e/g.test
  • String( value ).replace
  • Number
Code
( value ) => {

    if ( /e/g.test( value ) ) {

        return String( value ).replace( /\+/g, '' );

    } else {

        value = Number( value );

        return value + ( value % 1 ? '' : '.0' );

    }

}

NodeBuilder.getBindGroupsCache(): ChainMap

JSDoc:

/**
     * Returns the bind groups of the current renderer.
     *
     * @return {ChainMap} The cache.
     */

Returns: ChainMap

Calls:

  • rendererCache.get
  • rendererCache.set
Code
getBindGroupsCache() {

        let bindGroupsCache = rendererCache.get( this.renderer );

        if ( bindGroupsCache === undefined ) {

            bindGroupsCache = new ChainMap();

            rendererCache.set( this.renderer, bindGroupsCache );

        }

        return bindGroupsCache;

    }

NodeBuilder.createRenderTarget(width: number, height: number, options: any): RenderTarget

JSDoc:

/**
     * Factory method for creating an instance of {@link RenderTarget} with the given
     * dimensions and options.
     *
     * @param {number} width - The width of the render target.
     * @param {number} height - The height of the render target.
     * @param {Object} options - The options of the render target.
     * @return {RenderTarget} The render target.
     */

Parameters:

  • width number
  • height number
  • options any

Returns: RenderTarget

Code
createRenderTarget( width, height, options ) {

        return new RenderTarget( width, height, options );

    }

NodeBuilder.createCubeRenderTarget(size: number, options: any): CubeRenderTarget

JSDoc:

/**
     * Factory method for creating an instance of {@link CubeRenderTarget} with the given
     * dimensions and options.
     *
     * @param {number} size - The size of the cube render target.
     * @param {Object} options - The options of the cube render target.
     * @return {CubeRenderTarget} The cube render target.
     */

Parameters:

  • size number
  • options any

Returns: CubeRenderTarget

Code
createCubeRenderTarget( size, options ) {

        return new CubeRenderTarget( size, options );

    }

NodeBuilder.includes(node: Node): boolean

JSDoc:

/**
     * Whether the given node is included in the internal array of nodes or not.
     *
     * @param {Node} node - The node to test.
     * @return {boolean} Whether the given node is included in the internal array of nodes or not.
     */

Parameters:

  • node Node

Returns: boolean

Calls:

  • this.nodes.includes
Code
includes( node ) {

        return this.nodes.includes( node );

    }

NodeBuilder.getOutputStructName(): string

JSDoc:

/**
     * Returns the output struct name which is required by
     * {@link OutputStructNode}.
     *
     * @abstract
     * @return {string} The name of the output struct.
     */

Returns: string

Code
getOutputStructName() {}

NodeBuilder._getBindGroup(groupName: string, bindings: NodeUniformsGroup[]): BindGroup

JSDoc:

/**
     * Returns a bind group for the given group name and binding.
     *
     * @private
     * @param {string} groupName - The group name.
     * @param {Array<NodeUniformsGroup>} bindings - List of bindings.
     * @return {BindGroup} The bind group
     */

Parameters:

  • groupName string
  • bindings NodeUniformsGroup[]

Returns: BindGroup

Calls:

  • this.getBindGroupsCache
  • bindingsArray.push
  • bindGroupsCache.get
  • bindGroupsCache.set

Internal Comments:

// (x4)

Code
_getBindGroup( groupName, bindings ) {

        const bindGroupsCache = this.getBindGroupsCache();

        //

        const bindingsArray = [];

        let sharedGroup = true;

        for ( const binding of bindings ) {

            bindingsArray.push( binding );

            sharedGroup = sharedGroup && binding.groupNode.shared !== true;

        }

        //

        let bindGroup;

        if ( sharedGroup ) {

            bindGroup = bindGroupsCache.get( bindingsArray );

            if ( bindGroup === undefined ) {

                bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );

                bindGroupsCache.set( bindingsArray, bindGroup );

            }

        } else {

            bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );

        }

        return bindGroup;

    }

NodeBuilder.getBindGroupArray(groupName: string, shaderStage: "compute" | "vertex" | "fragment" | "any"): NodeUniformsGroup[]

JSDoc:

/**
     * Returns an array of node uniform groups for the given group name and shader stage.
     *
     * @param {string} groupName - The group name.
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {Array<NodeUniformsGroup>} The array of node uniform groups.
     */

Parameters:

  • groupName string
  • shaderStage "compute" | "vertex" | "fragment" | "any"

Returns: NodeUniformsGroup[]

Calls:

  • Object.keys
Code
getBindGroupArray( groupName, shaderStage ) {

        const bindings = this.bindings[ shaderStage ];

        let bindGroup = bindings[ groupName ];

        if ( bindGroup === undefined ) {

            if ( this.bindingsIndexes[ groupName ] === undefined ) {

                this.bindingsIndexes[ groupName ] = { binding: 0, group: Object.keys( this.bindingsIndexes ).length };

            }

            bindings[ groupName ] = bindGroup = [];

        }

        return bindGroup;

    }

NodeBuilder.getBindings(): BindGroup[]

JSDoc:

/**
     * Returns a list bindings of all shader stages separated by groups.
     *
     * @return {Array<BindGroup>} The list of bindings.
     */

Returns: BindGroup[]

Calls:

  • groupUniforms.push
  • this._getBindGroup
  • bindingsGroups.push
Code
getBindings() {

        let bindingsGroups = this.bindGroups;

        if ( bindingsGroups === null ) {

            const groups = {};
            const bindings = this.bindings;

            for ( const shaderStage of shaderStages ) {

                for ( const groupName in bindings[ shaderStage ] ) {

                    const uniforms = bindings[ shaderStage ][ groupName ];

                    const groupUniforms = groups[ groupName ] || ( groups[ groupName ] = [] );
                    groupUniforms.push( ...uniforms );

                }

            }

            bindingsGroups = [];

            for ( const groupName in groups ) {

                const group = groups[ groupName ];

                const bindingsGroup = this._getBindGroup( groupName, group );

                bindingsGroups.push( bindingsGroup );

            }

            this.bindGroups = bindingsGroups;

        }

        return bindingsGroups;

    }

NodeBuilder.sortBindingGroups(): void

JSDoc:

/**
     * Sorts the bind groups and updates {@link NodeBuilder#bindingsIndexes}.
     */

Returns: void

Calls:

  • this.getBindings
  • bindingsGroups.sort
Code
sortBindingGroups() {

        const bindingsGroups = this.getBindings();

        bindingsGroups.sort( ( a, b ) => ( a.bindings[ 0 ].groupNode.order - b.bindings[ 0 ].groupNode.order ) );

        for ( let i = 0; i < bindingsGroups.length; i ++ ) {

            const bindingGroup = bindingsGroups[ i ];
            this.bindingsIndexes[ bindingGroup.name ].group = i;

            bindingGroup.index = i;

        }

    }

NodeBuilder.setHashNode(node: Node, hash: number): void

JSDoc:

/**
     * The builder maintains each node in a hash-based dictionary.
     * This method sets the given node (value) with the given hash (key) into this dictionary.
     *
     * @param {Node} node - The node to add.
     * @param {number} hash - The hash of the node.
     */

Parameters:

  • node Node
  • hash number

Returns: void

Code
setHashNode( node, hash ) {

        this.hashNodes[ hash ] = node;

    }

NodeBuilder.addNode(node: Node): void

JSDoc:

/**
     * Adds a node to this builder.
     *
     * @param {Node} node - The node to add.
     */

Parameters:

  • node Node

Returns: void

Calls:

  • this.nodes.includes
  • this.nodes.push
  • this.setHashNode
  • node.getHash
Code
addNode( node ) {

        if ( this.nodes.includes( node ) === false ) {

            this.nodes.push( node );

            this.setHashNode( node, node.getHash( this ) );

        }

    }

NodeBuilder.addSequentialNode(node: Node): void

JSDoc:

/**
     * It is used to add Nodes that will be used as FRAME and RENDER events,
     * and need to follow a certain sequence in the calls to work correctly.
     * This function should be called after 'setup()' in the 'build()' process to ensure that the child nodes are processed first.
     *
     * @param {Node} node - The node to add.
     */

Parameters:

  • node Node

Returns: void

Calls:

  • this.sequentialNodes.includes
  • this.sequentialNodes.push
Code
addSequentialNode( node ) {

        if ( this.sequentialNodes.includes( node ) === false ) {

            this.sequentialNodes.push( node );

        }

    }

NodeBuilder.buildUpdateNodes(): void

JSDoc:

/**
     * Checks the update types of nodes
     */

Returns: void

Calls:

  • node.getUpdateType
  • this.updateNodes.push
  • node.getSelf
  • node.getUpdateBeforeType
  • node.getUpdateAfterType
  • this.updateBeforeNodes.push
  • this.updateAfterNodes.push
Code
buildUpdateNodes() {

        for ( const node of this.nodes ) {

            const updateType = node.getUpdateType();

            if ( updateType !== NodeUpdateType.NONE ) {

                this.updateNodes.push( node.getSelf() );

            }

        }

        for ( const node of this.sequentialNodes ) {

            const updateBeforeType = node.getUpdateBeforeType();
            const updateAfterType = node.getUpdateAfterType();

            if ( updateBeforeType !== NodeUpdateType.NONE ) {

                this.updateBeforeNodes.push( node.getSelf() );

            }

            if ( updateAfterType !== NodeUpdateType.NONE ) {

                this.updateAfterNodes.push( node.getSelf() );

            }

        }

    }

NodeBuilder.isFilteredTexture(texture: Texture): boolean

JSDoc:

/**
     * Whether the given texture is filtered or not.
     *
     * @param {Texture} texture - The texture to check.
     * @return {boolean} Whether the given texture is filtered or not.
     */

Parameters:

  • texture Texture

Returns: boolean

Code
isFilteredTexture( texture ) {

        return ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter ||
            texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter );

    }

NodeBuilder.addChain(node: Node): void

JSDoc:

/**
     * Adds the given node to the internal node chain.
     * This is used to check recursive calls in node-graph.
     *
     * @param {Node} node - The node to add.
     */

Parameters:

  • node Node

Returns: void

Calls:

  • this.chaining.push

Internal Comments:

/*
        if ( this.chaining.indexOf( node ) !== - 1 ) {

            console.warn( 'Recursive node: ', node );

        }
        */ (x5)

Code
addChain( node ) {

        /*
        if ( this.chaining.indexOf( node ) !== - 1 ) {

            console.warn( 'Recursive node: ', node );

        }
        */

        this.chaining.push( node );

    }

NodeBuilder.removeChain(node: Node): void

JSDoc:

/**
     * Removes the given node from the internal node chain.
     *
     * @param {Node} node - The node to remove.
     */

Parameters:

  • node Node

Returns: void

Calls:

  • this.chaining.pop
Code
removeChain( node ) {

        const lastChain = this.chaining.pop();

        if ( lastChain !== node ) {

            throw new Error( 'NodeBuilder: Invalid node chaining!' );

        }

    }

NodeBuilder.getMethod(method: string): string

JSDoc:

/**
     * Returns the native shader method name for a given generic name. E.g.
     * the method name `textureDimensions` matches the WGSL name but must be
     * resolved to `textureSize` in GLSL.
     *
     * @abstract
     * @param {string} method - The method name to resolve.
     * @return {string} The resolved method name.
     */

Parameters:

  • method string

Returns: string

Code
getMethod( method ) {

        return method;

    }

NodeBuilder.getTernary(): string

JSDoc:

/**
     * Returns the native snippet for a ternary operation. E.g. GLSL would output
     * a ternary op as `cond ? x : y` whereas WGSL would output it as `select(y, x, cond)`
     *
     * @abstract
     * @param {string} condSnippet - The condition determining which expression gets resolved.
     * @param {string} ifSnippet - The expression to resolve to if the condition is true.
     * @param {string} elseSnippet - The expression to resolve to if the condition is false.
     * @return {string} The resolved method name.
     */

Returns: string

Code
getTernary( /* condSnippet, ifSnippet, elseSnippet*/ ) {

        return null;

    }

NodeBuilder.getNodeFromHash(hash: number): Node

JSDoc:

/**
     * Returns a node for the given hash, see {@link NodeBuilder#setHashNode}.
     *
     * @param {number} hash - The hash of the node.
     * @return {Node} The found node.
     */

Parameters:

  • hash number

Returns: Node

Code
getNodeFromHash( hash ) {

        return this.hashNodes[ hash ];

    }

NodeBuilder.addFlow(shaderStage: "compute" | "vertex" | "fragment", node: Node): Node

JSDoc:

/**
     * Adds the Node to a target flow so that it can generate code in the 'generate' process.
     *
     * @param {('vertex'|'fragment'|'compute')} shaderStage - The shader stage.
     * @param {Node} node - The node to add.
     * @return {Node} The node.
     */

Parameters:

  • shaderStage "compute" | "vertex" | "fragment"
  • node Node

Returns: Node

Calls:

  • this.flowNodes[ shaderStage ].push
Code
addFlow( shaderStage, node ) {

        this.flowNodes[ shaderStage ].push( node );

        return node;

    }

NodeBuilder.setContext(context: any): void

JSDoc:

/**
     * Sets builder's context.
     *
     * @param {Object} context - The context to set.
     */

Parameters:

  • context any

Returns: void

Code
setContext( context ) {

        this.context = context;

    }

NodeBuilder.getContext(): any

JSDoc:

/**
     * Returns the builder's current context.
     *
     * @return {Object} The builder's current context.
     */

Returns: any

Code
getContext() {

        return this.context;

    }

NodeBuilder.getSharedContext(): any

JSDoc:

/**
     * Gets a context used in shader construction that can be shared across different materials.
     * This is necessary since the renderer cache can reuse shaders generated in one material and use them in another.
     *
     * @return {Object} The builder's current context without material.
     */

Returns: any

Code
getSharedContext() {

        const context = { ...this.context };

        delete context.material;

        return this.context;

    }

NodeBuilder.setCache(cache: NodeCache): void

JSDoc:

/**
     * Sets builder's cache.
     *
     * @param {NodeCache} cache - The cache to set.
     */

Parameters:

  • cache NodeCache

Returns: void

Code
setCache( cache ) {

        this.cache = cache;

    }

NodeBuilder.getCache(): NodeCache

JSDoc:

/**
     * Returns the builder's current cache.
     *
     * @return {NodeCache} The builder's current cache.
     */

Returns: NodeCache

Code
getCache() {

        return this.cache;

    }

NodeBuilder.getCacheFromNode(node: Node, parent: boolean): NodeCache

JSDoc:

/**
     * Returns a cache for the given node.
     *
     * @param {Node} node - The node.
     * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not.
     * @return {NodeCache} The cache.
     */

Parameters:

  • node Node
  • parent boolean

Returns: NodeCache

Calls:

  • this.getDataFromNode
  • this.getCache
Code
getCacheFromNode( node, parent = true ) {

        const data = this.getDataFromNode( node );
        if ( data.cache === undefined ) data.cache = new NodeCache( parent ? this.getCache() : null );

        return data.cache;

    }

NodeBuilder.isAvailable(): boolean

JSDoc:

/**
     * Whether the requested feature is available or not.
     *
     * @abstract
     * @param {string} name - The requested feature.
     * @return {boolean} Whether the requested feature is supported or not.
     */

Returns: boolean

Code
isAvailable( /*name*/ ) {

        return false;

    }

NodeBuilder.getVertexIndex(): string

JSDoc:

/**
     * Returns the vertexIndex input variable as a native shader string.
     *
     * @abstract
     * @return {string} The instanceIndex shader string.
     */

Returns: string

Calls:

  • console.warn
Code
getVertexIndex() {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.getInstanceIndex(): string

JSDoc:

/**
     * Returns the instanceIndex input variable as a native shader string.
     *
     * @abstract
     * @return {string} The instanceIndex shader string.
     */

Returns: string

Calls:

  • console.warn
Code
getInstanceIndex() {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.getDrawIndex(): string

JSDoc:

/**
     * Returns the drawIndex input variable as a native shader string.
     * Only relevant for WebGL and its `WEBGL_multi_draw` extension.
     *
     * @abstract
     * @return {?string} The drawIndex shader string.
     */

Returns: string

Calls:

  • console.warn
Code
getDrawIndex() {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.getFrontFacing(): string

JSDoc:

/**
     * Returns the frontFacing input variable as a native shader string.
     *
     * @abstract
     * @return {string} The frontFacing shader string.
     */

Returns: string

Calls:

  • console.warn
Code
getFrontFacing() {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.getFragCoord(): string

JSDoc:

/**
     * Returns the fragCoord input variable as a native shader string.
     *
     * @abstract
     * @return {string} The fragCoord shader string.
     */

Returns: string

Calls:

  • console.warn
Code
getFragCoord() {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.isFlipY(): boolean

JSDoc:

/**
     * Whether to flip texture data along its vertical axis or not. WebGL needs
     * this method evaluate to `true`, WebGPU to `false`.
     *
     * @abstract
     * @return {boolean} Whether to flip texture data along its vertical axis or not.
     */

Returns: boolean

Code
isFlipY() {

        return false;

    }

NodeBuilder.increaseUsage(node: Node): number

JSDoc:

/**
     * Calling this method increases the usage count for the given node by one.
     *
     * @param {Node} node - The node to increase the usage count for.
     * @return {number} The updated usage count.
     */

Parameters:

  • node Node

Returns: number

Calls:

  • this.getDataFromNode
Code
increaseUsage( node ) {

        const nodeData = this.getDataFromNode( node );
        nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1;

        return nodeData.usageCount;

    }

NodeBuilder.generateTexture(): string

JSDoc:

/**
     * Generates a texture sample shader string for the given texture data.
     *
     * @abstract
     * @param {Texture} texture - The texture.
     * @param {string} textureProperty - The texture property name.
     * @param {string} uvSnippet - Snippet defining the texture coordinates.
     * @return {string} The generated shader string.
     */

Returns: string

Calls:

  • console.warn
Code
generateTexture( /* texture, textureProperty, uvSnippet */ ) {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.generateTextureLod(): string

JSDoc:

/**
     * Generates a texture LOD shader string for the given texture data.
     *
     * @abstract
     * @param {Texture} texture - The texture.
     * @param {string} textureProperty - The texture property name.
     * @param {string} uvSnippet - Snippet defining the texture coordinates.
     * @param {?string} depthSnippet - Snippet defining the 0-based texture array index to sample.
     * @param {string} levelSnippet - Snippet defining the mip level.
     * @return {string} The generated shader string.
     */

Returns: string

Calls:

  • console.warn
Code
generateTextureLod( /* texture, textureProperty, uvSnippet, depthSnippet, levelSnippet */ ) {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.generateArrayDeclaration(type: string, count: number): string

JSDoc:

/**
     * Generates the array declaration string.
     *
     * @param {string} type - The type.
     * @param {?number} [count] - The count.
     * @return {string} The generated value as a shader string.
     */

Parameters:

  • type string
  • count number

Returns: string

Calls:

  • this.getType
Code
generateArrayDeclaration( type, count ) {

        return this.getType( type ) + '[ ' + count + ' ]';

    }

NodeBuilder.generateArray(type: string, count: number, values: Node[]): string

JSDoc:

/**
     * Generates the array shader string for the given type and value.
     *
     * @param {string} type - The type.
     * @param {?number} [count] - The count.
     * @param {?Array<Node>} [values=null] - The default values.
     * @return {string} The generated value as a shader string.
     */

Parameters:

  • type string
  • count number
  • values Node[]

Returns: string

Calls:

  • this.generateArrayDeclaration
  • value.build
  • this.generateConst
Code
generateArray( type, count, values = null ) {

        let snippet = this.generateArrayDeclaration( type, count ) + '( ';

        for ( let i = 0; i < count; i ++ ) {

            const value = values ? values[ i ] : null;

            if ( value !== null ) {

                snippet += value.build( this, type );

            } else {

                snippet += this.generateConst( type );

            }

            if ( i < count - 1 ) snippet += ', ';

        }

        snippet += ' )';

        return snippet;

    }

NodeBuilder.generateStruct(type: string, membersLayout: any[], values: Node[]): string

JSDoc:

/**
     * Generates the struct shader string.
     *
     * @param {string} type - The type.
     * @param {Array<Object>} [membersLayout] - The count.
     * @param {?Array<Node>} [values=null] - The default values.
     * @return {string} The generated value as a shader string.
     */

Parameters:

  • type string
  • membersLayout any[]
  • values Node[]

Returns: string

Calls:

  • snippets.push
  • values[ name ].build
  • this.generateConst
  • snippets.join
Code
generateStruct( type, membersLayout, values = null ) {

        const snippets = [];

        for ( const member of membersLayout ) {

            const { name, type } = member;

            if ( values && values[ name ] && values[ name ].isNode ) {

                snippets.push( values[ name ].build( this, type ) );

            } else {

                snippets.push( this.generateConst( type ) );

            }

        }

        return type + '( ' + snippets.join( ', ' ) + ' )';

    }

NodeBuilder.generateConst(type: string, value: any): string

JSDoc:

/**
     * Generates the shader string for the given type and value.
     *
     * @param {string} type - The type.
     * @param {?any} [value=null] - The value.
     * @return {string} The generated value as a shader string.
     */

Parameters:

  • type string
  • value any

Returns: string

Calls:

  • toFloat
  • Math.round
  • this.getType
  • this.getTypeLength
  • this.getComponentType
  • this.generateConst
  • generateConst
  • value.elements.map( generateConst ).join
Code
generateConst( type, value = null ) {

        if ( value === null ) {

            if ( type === 'float' || type === 'int' || type === 'uint' ) value = 0;
            else if ( type === 'bool' ) value = false;
            else if ( type === 'color' ) value = new Color();
            else if ( type === 'vec2' ) value = new Vector2();
            else if ( type === 'vec3' ) value = new Vector3();
            else if ( type === 'vec4' ) value = new Vector4();

        }

        if ( type === 'float' ) return toFloat( value );
        if ( type === 'int' ) return `${ Math.round( value ) }`;
        if ( type === 'uint' ) return value >= 0 ? `${ Math.round( value ) }u` : '0u';
        if ( type === 'bool' ) return value ? 'true' : 'false';
        if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ toFloat( value.r ) }, ${ toFloat( value.g ) }, ${ toFloat( value.b ) } )`;

        const typeLength = this.getTypeLength( type );

        const componentType = this.getComponentType( type );

        const generateConst = value => this.generateConst( componentType, value );

        if ( typeLength === 2 ) {

            return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) } )`;

        } else if ( typeLength === 3 ) {

            return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) } )`;

        } else if ( typeLength === 4 && type !== 'mat2' ) {

            return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) }, ${ generateConst( value.w ) } )`;

        } else if ( typeLength >= 4 && value && ( value.isMatrix2 || value.isMatrix3 || value.isMatrix4 ) ) {

            return `${ this.getType( type ) }( ${ value.elements.map( generateConst ).join( ', ' ) } )`;

        } else if ( typeLength > 4 ) {

            return `${ this.getType( type ) }()`;

        }

        throw new Error( `NodeBuilder: Type '${type}' not found in generate constant attempt.` );

    }

NodeBuilder.getType(type: string): string

JSDoc:

/**
     * It might be necessary to convert certain data types to different ones
     * so this method can be used to hide the conversion.
     *
     * @param {string} type - The type.
     * @return {string} The updated type.
     */

Parameters:

  • type string

Returns: string

Code
getType( type ) {

        if ( type === 'color' ) return 'vec3';

        return type;

    }

NodeBuilder.hasGeometryAttribute(name: string): boolean

JSDoc:

/**
     * Whether the given attribute name is defined in the geometry or not.
     *
     * @param {string} name - The attribute name.
     * @return {boolean} Whether the given attribute name is defined in the geometry.
     */

Parameters:

  • name string

Returns: boolean

Calls:

  • this.geometry.getAttribute
Code
hasGeometryAttribute( name ) {

        return this.geometry && this.geometry.getAttribute( name ) !== undefined;

    }

NodeBuilder.getAttribute(name: string, type: string): NodeAttribute

JSDoc:

/**
     * Returns a node attribute for the given name and type.
     *
     * @param {string} name - The attribute's name.
     * @param {string} type - The attribute's type.
     * @return {NodeAttribute} The node attribute.
     */

Parameters:

  • name string
  • type string

Returns: NodeAttribute

Calls:

  • this.registerDeclaration
  • attributes.push

Internal Comments:

// find attribute
// create a new if no exist (x2)

Code
getAttribute( name, type ) {

        const attributes = this.attributes;

        // find attribute

        for ( const attribute of attributes ) {

            if ( attribute.name === name ) {

                return attribute;

            }

        }

        // create a new if no exist

        const attribute = new NodeAttribute( name, type );

        this.registerDeclaration( attribute );

        attributes.push( attribute );

        return attribute;

    }

NodeBuilder.getPropertyName(node: Node): string

JSDoc:

/**
     * Returns for the given node and shader stage the property name for the shader.
     *
     * @param {Node} node - The node.
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The property name.
     */

Parameters:

  • node Node

Returns: string

Code
getPropertyName( node/*, shaderStage*/ ) {

        return node.name;

    }

NodeBuilder.isVector(type: string): boolean

JSDoc:

/**
     * Whether the given type is a vector type or not.
     *
     * @param {string} type - The type to check.
     * @return {boolean} Whether the given type is a vector type or not.
     */

Parameters:

  • type string

Returns: boolean

Calls:

  • /vec\d/.test
Code
isVector( type ) {

        return /vec\d/.test( type );

    }

NodeBuilder.isMatrix(type: string): boolean

JSDoc:

/**
     * Whether the given type is a matrix type or not.
     *
     * @param {string} type - The type to check.
     * @return {boolean} Whether the given type is a matrix type or not.
     */

Parameters:

  • type string

Returns: boolean

Calls:

  • /mat\d/.test
Code
isMatrix( type ) {

        return /mat\d/.test( type );

    }

NodeBuilder.isReference(type: string): boolean

JSDoc:

/**
     * Whether the given type is a reference type or not.
     *
     * @param {string} type - The type to check.
     * @return {boolean} Whether the given type is a reference type or not.
     */

Parameters:

  • type string

Returns: boolean

Code
isReference( type ) {

        return type === 'void' || type === 'property' || type === 'sampler' || type === 'samplerComparison' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'depthTexture' || type === 'texture3D';

    }

NodeBuilder.needsToWorkingColorSpace(): boolean

JSDoc:

/**
     * Checks if the given texture requires a manual conversion to the working color space.
     *
     * @abstract
     * @param {Texture} texture - The texture to check.
     * @return {boolean} Whether the given texture requires a conversion to working color space or not.
     */

Returns: boolean

Code
needsToWorkingColorSpace( /*texture*/ ) {

        return false;

    }

NodeBuilder.getComponentTypeFromTexture(texture: Texture): string

JSDoc:

/**
     * Returns the component type of a given texture.
     *
     * @param {Texture} texture - The texture.
     * @return {string} The component type.
     */

Parameters:

  • texture Texture

Returns: string

Code
getComponentTypeFromTexture( texture ) {

        const type = texture.type;

        if ( texture.isDataTexture ) {

            if ( type === IntType ) return 'int';
            if ( type === UnsignedIntType ) return 'uint';

        }

        return 'float';

    }

NodeBuilder.getElementType(type: string): string

JSDoc:

/**
     * Returns the element type for a given type.
     *
     * @param {string} type - The type.
     * @return {string} The element type.
     */

Parameters:

  • type string

Returns: string

Calls:

  • this.getComponentType
Code
getElementType( type ) {

        if ( type === 'mat2' ) return 'vec2';
        if ( type === 'mat3' ) return 'vec3';
        if ( type === 'mat4' ) return 'vec4';

        return this.getComponentType( type );

    }

NodeBuilder.getComponentType(type: string): string

JSDoc:

/**
     * Returns the component type for a given type.
     *
     * @param {string} type - The type.
     * @return {string} The component type.
     */

Parameters:

  • type string

Returns: string

Calls:

  • this.getVectorType
  • /(b|i|u|)(vec|mat)([2-4])/.exec
Code
getComponentType( type ) {

        type = this.getVectorType( type );

        if ( type === 'float' || type === 'bool' || type === 'int' || type === 'uint' ) return type;

        const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec( type );

        if ( componentType === null ) return null;

        if ( componentType[ 1 ] === 'b' ) return 'bool';
        if ( componentType[ 1 ] === 'i' ) return 'int';
        if ( componentType[ 1 ] === 'u' ) return 'uint';

        return 'float';

    }

NodeBuilder.getVectorType(type: string): string

JSDoc:

/**
     * Returns the vector type for a given type.
     *
     * @param {string} type - The type.
     * @return {string} The vector type.
     */

Parameters:

  • type string

Returns: string

Code
getVectorType( type ) {

        if ( type === 'color' ) return 'vec3';
        if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) return 'vec4';

        return type;

    }

NodeBuilder.getTypeFromLength(length: number, componentType: string): string

JSDoc:

/**
     * Returns the data type for the given the length and component type.
     *
     * @param {number} length - The length.
     * @param {string} [componentType='float'] - The component type.
     * @return {string} The type.
     */

Parameters:

  • length number
  • componentType string

Returns: string

Calls:

  • getTypeFromLength (from ./NodeUtils.js)
  • /mat2/.test
  • baseType.replace

Internal Comments:

// fix edge case for mat2x2 being same size as vec4

Code
getTypeFromLength( length, componentType = 'float' ) {

        if ( length === 1 ) return componentType;

        let baseType = getTypeFromLength( length );
        const prefix = componentType === 'float' ? '' : componentType[ 0 ];

        // fix edge case for mat2x2 being same size as vec4
        if ( /mat2/.test( componentType ) === true ) {

            baseType = baseType.replace( 'vec', 'mat' );

        }

        return prefix + baseType;

    }

NodeBuilder.getTypeFromArray(array: TypedArray): string

JSDoc:

/**
     * Returns the type for a given typed array.
     *
     * @param {TypedArray} array - The typed array.
     * @return {string} The type.
     */

Parameters:

  • array TypedArray

Returns: string

Calls:

  • typeFromArray.get
Code
getTypeFromArray( array ) {

        return typeFromArray.get( array.constructor );

    }

NodeBuilder.isInteger(type: string): boolean

JSDoc:

/**
     * Returns the type is an integer type.
     *
     * @param {string} type - The type.
     * @return {boolean} Whether the type is an integer type or not.
     */

Parameters:

  • type string

Returns: boolean

Calls:

  • /int|uint|(i|u)vec/.test
Code
isInteger( type ) {

        return /int|uint|(i|u)vec/.test( type );

    }

NodeBuilder.getTypeFromAttribute(attribute: BufferAttribute): string

JSDoc:

/**
     * Returns the type for a given buffer attribute.
     *
     * @param {BufferAttribute} attribute - The buffer attribute.
     * @return {string} The type.
     */

Parameters:

  • attribute BufferAttribute

Returns: string

Calls:

  • this.getTypeFromArray
  • this.getTypeFromLength
Code
getTypeFromAttribute( attribute ) {

        let dataAttribute = attribute;

        if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data;

        const array = dataAttribute.array;
        const itemSize = attribute.itemSize;
        const normalized = attribute.normalized;

        let arrayType;

        if ( ! ( attribute instanceof Float16BufferAttribute ) && normalized !== true ) {

            arrayType = this.getTypeFromArray( array );

        }

        return this.getTypeFromLength( itemSize, arrayType );

    }

NodeBuilder.getTypeLength(type: string): number

JSDoc:

/**
     * Returns the length for the given data type.
     *
     * @param {string} type - The data type.
     * @return {number} The length.
     */

Parameters:

  • type string

Returns: number

Calls:

  • this.getVectorType
  • /vec([2-4])/.exec
  • Number
  • /mat2/.test
  • /mat3/.test
  • /mat4/.test
Code
getTypeLength( type ) {

        const vecType = this.getVectorType( type );
        const vecNum = /vec([2-4])/.exec( vecType );

        if ( vecNum !== null ) return Number( vecNum[ 1 ] );
        if ( vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint' ) return 1;
        if ( /mat2/.test( type ) === true ) return 4;
        if ( /mat3/.test( type ) === true ) return 9;
        if ( /mat4/.test( type ) === true ) return 16;

        return 0;

    }

NodeBuilder.getVectorFromMatrix(type: string): string

JSDoc:

/**
     * Returns the vector type for a given matrix type.
     *
     * @param {string} type - The matrix type.
     * @return {string} The vector type.
     */

Parameters:

  • type string

Returns: string

Calls:

  • type.replace
Code
getVectorFromMatrix( type ) {

        return type.replace( 'mat', 'vec' );

    }

NodeBuilder.changeComponentType(type: string, newComponentType: string): string

JSDoc:

/**
     * For a given type this method changes the component type to the
     * given value. E.g. `vec4` should be changed to the new component type
     * `uint` which results in `uvec4`.
     *
     * @param {string} type - The type.
     * @param {string} newComponentType - The new component type.
     * @return {string} The new type.
     */

Parameters:

  • type string
  • newComponentType string

Returns: string

Calls:

  • this.getTypeFromLength
  • this.getTypeLength
Code
changeComponentType( type, newComponentType ) {

        return this.getTypeFromLength( this.getTypeLength( type ), newComponentType );

    }

NodeBuilder.getIntegerType(type: string): string

JSDoc:

/**
     * Returns the integer type pendant for the given type.
     *
     * @param {string} type - The type.
     * @return {string} The integer type.
     */

Parameters:

  • type string

Returns: string

Calls:

  • this.getComponentType
  • this.changeComponentType
Code
getIntegerType( type ) {

        const componentType = this.getComponentType( type );

        if ( componentType === 'int' || componentType === 'uint' ) return type;

        return this.changeComponentType( type, 'int' );

    }

NodeBuilder.addStack(): StackNode

JSDoc:

/**
     * Adds a stack node to the internal stack.
     *
     * @return {StackNode} The added stack node.
     */

Returns: StackNode

Calls:

  • stack (from ./StackNode.js)
  • this.stacks.push
  • getCurrentStack (from ../tsl/TSLBase.js)
  • setCurrentStack (from ../tsl/TSLBase.js)
Code
addStack() {

        this.stack = stack( this.stack );

        this.stacks.push( getCurrentStack() || this.stack );
        setCurrentStack( this.stack );

        return this.stack;

    }

NodeBuilder.removeStack(): StackNode

JSDoc:

/**
     * Removes the last stack node from the internal stack.
     *
     * @return {StackNode} The removed stack node.
     */

Returns: StackNode

Calls:

  • setCurrentStack (from ../tsl/TSLBase.js)
  • this.stacks.pop
Code
removeStack() {

        const lastStack = this.stack;
        this.stack = lastStack.parent;

        setCurrentStack( this.stacks.pop() );

        return lastStack;

    }

NodeBuilder.getDataFromNode(node: Node, shaderStage: "compute" | "vertex" | "fragment" | "any", cache: NodeCache): any

JSDoc:

/**
     * The builder maintains (cached) data for each node during the building process. This method
     * can be used to get these data for a specific shader stage and cache.
     *
     * @param {Node} node - The node to get the data for.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @param {?NodeCache} cache - An optional cache.
     * @return {Object} The node data.
     */

Parameters:

  • node Node
  • shaderStage "compute" | "vertex" | "fragment" | "any"
  • cache NodeCache

Returns: any

Calls:

  • node.isGlobal
  • cache.getData
  • cache.setData
  • this.getClosestSubBuild

Internal Comments:

// (x2)

Code
getDataFromNode( node, shaderStage = this.shaderStage, cache = null ) {

        cache = cache === null ? ( node.isGlobal( this ) ? this.globalCache : this.cache ) : cache;

        let nodeData = cache.getData( node );

        if ( nodeData === undefined ) {

            nodeData = {};

            cache.setData( node, nodeData );

        }

        if ( nodeData[ shaderStage ] === undefined ) nodeData[ shaderStage ] = {};

        //

        let data = nodeData[ shaderStage ];

        const subBuilds = nodeData.any ? nodeData.any.subBuilds : null;
        const subBuild = this.getClosestSubBuild( subBuilds );

        if ( subBuild ) {

            if ( data.subBuildsCache === undefined ) data.subBuildsCache = {};

            data = data.subBuildsCache[ subBuild ] || ( data.subBuildsCache[ subBuild ] = {} );
            data.subBuilds = subBuilds;

        }

        return data;

    }

NodeBuilder.getNodeProperties(node: Node, shaderStage: "compute" | "vertex" | "fragment" | "any"): any

JSDoc:

/**
     * Returns the properties for the given node and shader stage.
     *
     * @param {Node} node - The node to get the properties for.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage='any'] - The shader stage.
     * @return {Object} The node properties.
     */

Parameters:

  • node Node
  • shaderStage "compute" | "vertex" | "fragment" | "any"

Returns: any

Calls:

  • this.getDataFromNode
Code
getNodeProperties( node, shaderStage = 'any' ) {

        const nodeData = this.getDataFromNode( node, shaderStage );

        return nodeData.properties || ( nodeData.properties = { outputNode: null } );

    }

NodeBuilder.getBufferAttributeFromNode(node: BufferAttributeNode, type: string): NodeAttribute

JSDoc:

/**
     * Returns an instance of {@link NodeAttribute} for the given buffer attribute node.
     *
     * @param {BufferAttributeNode} node - The buffer attribute node.
     * @param {string} type - The node type.
     * @return {NodeAttribute} The node attribute.
     */

Parameters:

  • node BufferAttributeNode
  • type string

Returns: NodeAttribute

Calls:

  • this.getDataFromNode
  • this.bufferAttributes.push
Code
getBufferAttributeFromNode( node, type ) {

        const nodeData = this.getDataFromNode( node );

        let bufferAttribute = nodeData.bufferAttribute;

        if ( bufferAttribute === undefined ) {

            const index = this.uniforms.index ++;

            bufferAttribute = new NodeAttribute( 'nodeAttribute' + index, type, node );

            this.bufferAttributes.push( bufferAttribute );

            nodeData.bufferAttribute = bufferAttribute;

        }

        return bufferAttribute;

    }

NodeBuilder.getStructTypeFromNode(node: OutputStructNode, membersLayout: any[], name: string, shaderStage: "compute" | "vertex" | "fragment" | "any"): StructType

JSDoc:

/**
     * Returns an instance of {@link StructType} for the given output struct node.
     *
     * @param {OutputStructNode} node - The output struct node.
     * @param {Array<Object>} membersLayout - The output struct types.
     * @param {?string} [name=null] - The name of the struct.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @return {StructType} The struct type attribute.
     */

Parameters:

  • node OutputStructNode
  • membersLayout any[]
  • name string
  • shaderStage "compute" | "vertex" | "fragment" | "any"

Returns: StructType

Calls:

  • this.getDataFromNode
  • this.structs[ shaderStage ].push
Code
getStructTypeFromNode( node, membersLayout, name = null, shaderStage = this.shaderStage ) {

        const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );

        let structType = nodeData.structType;

        if ( structType === undefined ) {

            const index = this.structs.index ++;

            if ( name === null ) name = 'StructType' + index;

            structType = new StructType( name, membersLayout );

            this.structs[ shaderStage ].push( structType );

            nodeData.structType = structType;

        }

        return structType;

    }

NodeBuilder.getOutputStructTypeFromNode(node: OutputStructNode, membersLayout: any[]): StructType

JSDoc:

/**
     * Returns an instance of {@link StructType} for the given output struct node.
     *
     * @param {OutputStructNode} node - The output struct node.
     * @param {Array<Object>} membersLayout - The output struct types.
     * @return {StructType} The struct type attribute.
     */

Parameters:

  • node OutputStructNode
  • membersLayout any[]

Returns: StructType

Calls:

  • this.getStructTypeFromNode
Code
getOutputStructTypeFromNode( node, membersLayout ) {

        const structType = this.getStructTypeFromNode( node, membersLayout, 'OutputType', 'fragment' );
        structType.output = true;

        return structType;

    }

NodeBuilder.getUniformFromNode(node: UniformNode, type: string, shaderStage: "compute" | "vertex" | "fragment" | "any", name: string): NodeUniform

JSDoc:

/**
     * Returns an instance of {@link NodeUniform} for the given uniform node.
     *
     * @param {UniformNode} node - The uniform node.
     * @param {string} type - The uniform type.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @param {?string} name - The name of the uniform.
     * @return {NodeUniform} The node uniform.
     */

Parameters:

  • node UniformNode
  • type string
  • shaderStage "compute" | "vertex" | "fragment" | "any"
  • name string

Returns: NodeUniform

Calls:

  • this.getDataFromNode
  • this.uniforms[ shaderStage ].push
  • this.registerDeclaration
Code
getUniformFromNode( node, type, shaderStage = this.shaderStage, name = null ) {

        const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );

        let nodeUniform = nodeData.uniform;

        if ( nodeUniform === undefined ) {

            const index = this.uniforms.index ++;

            nodeUniform = new NodeUniform( name || ( 'nodeUniform' + index ), type, node );

            this.uniforms[ shaderStage ].push( nodeUniform );

            this.registerDeclaration( nodeUniform );

            nodeData.uniform = nodeUniform;

        }

        return nodeUniform;

    }

NodeBuilder.getVarFromNode(node: VarNode, name: string, type: string, shaderStage: "compute" | "vertex" | "fragment" | "any", readOnly: boolean): NodeVar

JSDoc:

/**
     * Returns an instance of {@link NodeVar} for the given variable node.
     *
     * @param {VarNode} node - The variable node.
     * @param {?string} name - The variable's name.
     * @param {string} [type=node.getNodeType( this )] - The variable's type.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @param {boolean} [readOnly=false] - Whether the variable is read-only or not.
     *
     * @return {NodeVar} The node variable.
     */

Parameters:

  • node VarNode
  • name string
  • type string
  • shaderStage "compute" | "vertex" | "fragment" | "any"
  • readOnly boolean

Returns: NodeVar

Calls:

  • this.getDataFromNode
  • this.getSubBuildProperty
  • node.getArrayCount
  • vars.push
  • this.registerDeclaration

Internal Comments:

// (x3)

Code
getVarFromNode( node, name = null, type = node.getNodeType( this ), shaderStage = this.shaderStage, readOnly = false ) {

        const nodeData = this.getDataFromNode( node, shaderStage );
        const subBuildVariable = this.getSubBuildProperty( 'variable', nodeData.subBuilds );

        let nodeVar = nodeData[ subBuildVariable ];

        if ( nodeVar === undefined ) {

            const idNS = readOnly ? '_const' : '_var';

            const vars = this.vars[ shaderStage ] || ( this.vars[ shaderStage ] = [] );
            const id = this.vars[ idNS ] || ( this.vars[ idNS ] = 0 );

            if ( name === null ) {

                name = ( readOnly ? 'nodeConst' : 'nodeVar' ) + id;

                this.vars[ idNS ] ++;

            }

            //

            if ( subBuildVariable !== 'variable' ) {

                name = this.getSubBuildProperty( name, nodeData.subBuilds );

            }

            //

            const count = node.getArrayCount( this );

            nodeVar = new NodeVar( name, type, readOnly, count );

            if ( ! readOnly ) {

                vars.push( nodeVar );

            }

            this.registerDeclaration( nodeVar );

            nodeData[ subBuildVariable ] = nodeVar;

        }

        return nodeVar;

    }

NodeBuilder.isDeterministic(node: Node): boolean

JSDoc:

/**
     * Returns whether a Node or its flow is deterministic, useful for use in `const`.
     *
     * @param {Node} node - The varying node.
     * @return {boolean} Returns true if deterministic.
     */

Parameters:

  • node Node

Returns: boolean

Calls:

  • this.isDeterministic
Code
isDeterministic( node ) {

        if ( node.isMathNode ) {

            return this.isDeterministic( node.aNode ) &&
                ( node.bNode ? this.isDeterministic( node.bNode ) : true ) &&
                ( node.cNode ? this.isDeterministic( node.cNode ) : true );

        } else if ( node.isOperatorNode ) {

            return this.isDeterministic( node.aNode ) &&
                ( node.bNode ? this.isDeterministic( node.bNode ) : true );

        } else if ( node.isArrayNode ) {

            if ( node.values !== null ) {

                for ( const n of node.values ) {

                    if ( ! this.isDeterministic( n ) ) {

                        return false;

                    }

                }

            }

            return true;

        } else if ( node.isConstNode ) {

            return true;

        }

        return false;

    }

NodeBuilder.getVaryingFromNode(node: any, name: string, type: string, interpolationType: string, interpolationSampling: string): NodeVar

JSDoc:

/**
     * Returns an instance of {@link NodeVarying} for the given varying node.
     *
     * @param {(VaryingNode|PropertyNode)} node - The varying node.
     * @param {?string} name - The varying's name.
     * @param {string} [type=node.getNodeType( this )] - The varying's type.
     * @param {?string} interpolationType - The interpolation type of the varying.
     * @param {?string} interpolationSampling - The interpolation sampling type of the varying.
     * @return {NodeVar} The node varying.
     */

Parameters:

  • node any
  • name string
  • type string
  • interpolationType string
  • interpolationSampling string

Returns: NodeVar

Calls:

  • this.getDataFromNode
  • this.getSubBuildProperty
  • varyings.push
  • this.registerDeclaration

Internal Comments:

// (x4)

Code
getVaryingFromNode( node, name = null, type = node.getNodeType( this ), interpolationType = null, interpolationSampling = null ) {

        const nodeData = this.getDataFromNode( node, 'any' );
        const subBuildVarying = this.getSubBuildProperty( 'varying', nodeData.subBuilds );

        let nodeVarying = nodeData[ subBuildVarying ];

        if ( nodeVarying === undefined ) {

            const varyings = this.varyings;
            const index = varyings.length;

            if ( name === null ) name = 'nodeVarying' + index;

            //

            if ( subBuildVarying !== 'varying' ) {

                name = this.getSubBuildProperty( name, nodeData.subBuilds );

            }

            //

            nodeVarying = new NodeVarying( name, type, interpolationType, interpolationSampling );

            varyings.push( nodeVarying );

            this.registerDeclaration( nodeVarying );

            nodeData[ subBuildVarying ] = nodeVarying;

        }

        return nodeVarying;

    }

NodeBuilder.registerDeclaration(node: any): void

JSDoc:

/**
     * Registers a node declaration in the current shader stage.
     *
     * @param {Object} node - The node to be registered.
     */

Parameters:

  • node any

Returns: void

Calls:

  • this.getPropertyName
  • console.warn

Internal Comments:

// Automatically renames the property if the name is already in use.

Code
registerDeclaration( node ) {

        const shaderStage = this.shaderStage;
        const declarations = this.declarations[ shaderStage ] || ( this.declarations[ shaderStage ] = {} );

        const property = this.getPropertyName( node );

        let index = 1;
        let name = property;

        // Automatically renames the property if the name is already in use.

        while ( declarations[ name ] !== undefined ) {

            name = property + '_' + index ++;

        }

        if ( index > 1 ) {

            node.name = name;

            console.warn( `THREE.TSL: Declaration name '${ property }' of '${ node.type }' already in use. Renamed to '${ name }'.` );

        }

        declarations[ name ] = node;

    }

NodeBuilder.getCodeFromNode(node: CodeNode, type: string, shaderStage: "compute" | "vertex" | "fragment" | "any"): NodeCode

JSDoc:

/**
     * Returns an instance of {@link NodeCode} for the given code node.
     *
     * @param {CodeNode} node - The code node.
     * @param {string} type - The node type.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @return {NodeCode} The node code.
     */

Parameters:

  • node CodeNode
  • type string
  • shaderStage "compute" | "vertex" | "fragment" | "any"

Returns: NodeCode

Calls:

  • this.getDataFromNode
  • codes.push
Code
getCodeFromNode( node, type, shaderStage = this.shaderStage ) {

        const nodeData = this.getDataFromNode( node );

        let nodeCode = nodeData.code;

        if ( nodeCode === undefined ) {

            const codes = this.codes[ shaderStage ] || ( this.codes[ shaderStage ] = [] );
            const index = codes.length;

            nodeCode = new NodeCode( 'nodeCode' + index, type );

            codes.push( nodeCode );

            nodeData.code = nodeCode;

        }

        return nodeCode;

    }

NodeBuilder.addFlowCodeHierarchy(node: Node, nodeBlock: Node): void

JSDoc:

/**
     * Adds a code flow based on the code-block hierarchy.

     * This is used so that code-blocks like If,Else create their variables locally if the Node
     * is only used inside one of these conditionals in the current shader stage.
     *
     * @param {Node} node - The node to add.
     * @param {Node} nodeBlock - Node-based code-block. Usually 'ConditionalNode'.
     */

Parameters:

  • node Node
  • nodeBlock Node

Returns: void

Calls:

  • this.getDataFromNode
  • flowCodeBlock.get
  • this.addLineFlowCode
Code
addFlowCodeHierarchy( node, nodeBlock ) {

        const { flowCodes, flowCodeBlock } = this.getDataFromNode( node );

        let needsFlowCode = true;
        let nodeBlockHierarchy = nodeBlock;

        while ( nodeBlockHierarchy ) {

            if ( flowCodeBlock.get( nodeBlockHierarchy ) === true ) {

                needsFlowCode = false;
                break;

            }

            nodeBlockHierarchy = this.getDataFromNode( nodeBlockHierarchy ).parentNodeBlock;

        }

        if ( needsFlowCode ) {

            for ( const flowCode of flowCodes ) {

                this.addLineFlowCode( flowCode );

            }

        }

    }

NodeBuilder.addLineFlowCodeBlock(node: Node, code: string, nodeBlock: Node): void

JSDoc:

/**
     * Add a inline-code to the current flow code-block.
     *
     * @param {Node} node - The node to add.
     * @param {string} code - The code to add.
     * @param {Node} nodeBlock - Current ConditionalNode
     */

Parameters:

  • node Node
  • code string
  • nodeBlock Node

Returns: void

Calls:

  • this.getDataFromNode
  • flowCodes.push
  • codeBlock.set
Code
addLineFlowCodeBlock( node, code, nodeBlock ) {

        const nodeData = this.getDataFromNode( node );
        const flowCodes = nodeData.flowCodes || ( nodeData.flowCodes = [] );
        const codeBlock = nodeData.flowCodeBlock || ( nodeData.flowCodeBlock = new WeakMap() );

        flowCodes.push( code );
        codeBlock.set( nodeBlock, true );

    }

NodeBuilder.addLineFlowCode(code: string, node: Node): NodeBuilder

JSDoc:

/**
     * Add a inline-code to the current flow.
     *
     * @param {string} code - The code to add.
     * @param {?Node} [node= null] - Optional Node, can help the system understand if the Node is part of a code-block.
     * @return {NodeBuilder} A reference to this node builder.
     */

Parameters:

  • code string
  • node Node

Returns: NodeBuilder

Calls:

  • this.addLineFlowCodeBlock
  • /;\s*$/.test
Code
addLineFlowCode( code, node = null ) {

        if ( code === '' ) return this;

        if ( node !== null && this.context.nodeBlock ) {

            this.addLineFlowCodeBlock( node, code, this.context.nodeBlock );

        }

        code = this.tab + code;

        if ( ! /;\s*$/.test( code ) ) {

            code = code + ';\n';

        }

        this.flow.code += code;

        return this;

    }

NodeBuilder.addFlowCode(code: string): NodeBuilder

JSDoc:

/**
     * Adds a code to the current code flow.
     *
     * @param {string} code - Shader code.
     * @return {NodeBuilder} A reference to this node builder.
     */

Parameters:

  • code string

Returns: NodeBuilder

Code
addFlowCode( code ) {

        this.flow.code += code;

        return this;

    }

NodeBuilder.addFlowTab(): NodeBuilder

JSDoc:

/**
     * Add tab in the code that will be generated so that other snippets respect the current tabulation.
     * Typically used in codes with If,Else.
     *
     * @return {NodeBuilder} A reference to this node builder.
     */

Returns: NodeBuilder

Code
addFlowTab() {

        this.tab += '\t';

        return this;

    }

NodeBuilder.removeFlowTab(): NodeBuilder

JSDoc:

/**
     * Removes a tab.
     *
     * @return {NodeBuilder} A reference to this node builder.
     */

Returns: NodeBuilder

Calls:

  • this.tab.slice
Code
removeFlowTab() {

        this.tab = this.tab.slice( 0, - 1 );

        return this;

    }

NodeBuilder.getFlowData(node: Node): any

JSDoc:

/**
     * Gets the current flow data based on a Node.
     *
     * @param {Node} node - Node that the flow was started.
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {Object} The flow data.
     */

Parameters:

  • node Node

Returns: any

Calls:

  • this.flowsData.get
Code
getFlowData( node/*, shaderStage*/ ) {

        return this.flowsData.get( node );

    }

NodeBuilder.flowNode(node: Node): any

JSDoc:

/**
     * Executes the node flow based on a root node to generate the final shader code.
     *
     * @param {Node} node - The node to execute.
     * @return {Object} The code flow.
     */

Parameters:

  • node Node

Returns: any

Calls:

  • node.getNodeType
  • this.flowChildNode
  • this.flowsData.set
Code
flowNode( node ) {

        const output = node.getNodeType( this );

        const flowData = this.flowChildNode( node, output );

        this.flowsData.set( node, flowData );

        return flowData;

    }

NodeBuilder.addInclude(node: Node): void

JSDoc:

/**
     * Includes a node in the current function node.
     *
     * @param {Node} node - The node to include.
     * @returns {void}
     */

Parameters:

  • node Node

Returns: void

Calls:

  • this.currentFunctionNode.includes.push
Code
addInclude( node ) {

        if ( this.currentFunctionNode !== null ) {

            this.currentFunctionNode.includes.push( node );

        }

    }

NodeBuilder.buildFunctionNode(shaderNode: ShaderNodeInternal): FunctionNode

JSDoc:

/**
     * Returns the native shader operator name for a given generic name.
     * It is a similar type of method like {@link NodeBuilder#getMethod}.
     *
     * @param {ShaderNodeInternal} shaderNode - The shader node to build the function node with.
     * @return {FunctionNode} The build function node.
     */

Parameters:

  • shaderNode ShaderNodeInternal

Returns: FunctionNode

Calls:

  • this.buildFunctionCode
Code
buildFunctionNode( shaderNode ) {

        const fn = new FunctionNode();

        const previous = this.currentFunctionNode;

        this.currentFunctionNode = fn;

        fn.code = this.buildFunctionCode( shaderNode );

        this.currentFunctionNode = previous;

        return fn;

    }

NodeBuilder.flowShaderNode(shaderNode: ShaderNodeInternal): any

JSDoc:

/**
     * Generates a code flow based on a TSL function: Fn().
     *
     * @param {ShaderNodeInternal} shaderNode - A function code will be generated based on the input.
     * @return {Object}
     */

Parameters:

  • shaderNode ShaderNodeInternal

Returns: any

Calls:

  • Object.values
  • shaderNode.call
  • this.flowStagesNode

Internal Comments:

// (x4)

Code
flowShaderNode( shaderNode ) {

        const layout = shaderNode.layout;

        const inputs = {
            [ Symbol.iterator ]() {

                let index = 0;
                const values = Object.values( this );
                return {
                    next: () => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )
                };

            }
        };

        for ( const input of layout.inputs ) {

            inputs[ input.name ] = new ParameterNode( input.type, input.name );

        }

        //

        shaderNode.layout = null;

        const callNode = shaderNode.call( inputs );
        const flowData = this.flowStagesNode( callNode, layout.type );

        shaderNode.layout = layout;

        return flowData;

    }

NodeBuilder.flowBuildStage(node: Node, buildStage: string, output: string | Node): string | Node

JSDoc:

/**
     * Executes the node in a specific build stage.
     *
     * @param {Node} node - The node to execute.
     * @param {string} buildStage - The build stage to execute the node in.
     * @param {Node|string|null} output - Expected output type. For example 'vec3'.
     * @return {Node|string|null} The result of the node build.
     */

Parameters:

  • node Node
  • buildStage string
  • output string | Node

Returns: string | Node

Calls:

  • this.getBuildStage
  • this.setBuildStage
  • node.build
Code
flowBuildStage( node, buildStage, output = null ) {

        const previousBuildStage = this.getBuildStage();

        this.setBuildStage( buildStage );

        const result = node.build( this, output );

        this.setBuildStage( previousBuildStage );

        return result;

    }

NodeBuilder.flowStagesNode(node: Node, output: string): any

JSDoc:

/**
     * Runs the node flow through all the steps of creation, 'setup', 'analyze', 'generate'.
     *
     * @param {Node} node - The node to execute.
     * @param {?string} output - Expected output type. For example 'vec3'.
     * @return {Object}
     */

Parameters:

  • node Node
  • output string

Returns: any

Calls:

  • stack (from ./StackNode.js)
  • this.setBuildStage
  • node.build
  • this.getVars
Code
flowStagesNode( node, output = null ) {

        const previousFlow = this.flow;
        const previousVars = this.vars;
        const previousDeclarations = this.declarations;
        const previousCache = this.cache;
        const previousBuildStage = this.buildStage;
        const previousStack = this.stack;

        const flow = {
            code: ''
        };

        this.flow = flow;
        this.vars = {};
        this.declarations = {};
        this.cache = new NodeCache();
        this.stack = stack();

        for ( const buildStage of defaultBuildStages ) {

            this.setBuildStage( buildStage );

            flow.result = node.build( this, output );

        }

        flow.vars = this.getVars( this.shaderStage );

        this.flow = previousFlow;
        this.vars = previousVars;
        this.declarations = previousDeclarations;
        this.cache = previousCache;
        this.stack = previousStack;

        this.setBuildStage( previousBuildStage );

        return flow;

    }

NodeBuilder.getFunctionOperator(): string

JSDoc:

/**
     * Returns the native shader operator name for a given generic name.
     * It is a similar type of method like {@link NodeBuilder#getMethod}.
     *
     * @abstract
     * @param {string} op - The operator name to resolve.
     * @return {?string} The resolved operator name.
     */

Returns: string

Code
getFunctionOperator( /* op */ ) {

        return null;

    }

NodeBuilder.buildFunctionCode(): string

JSDoc:

/**
     * Builds the given shader node.
     *
     * @abstract
     * @param {ShaderNodeInternal} shaderNode - The shader node.
     * @return {string} The function code.
     */

Returns: string

Calls:

  • console.warn
Code
buildFunctionCode( /* shaderNode */ ) {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.flowChildNode(node: Node, output: string): any

JSDoc:

/**
     * Generates a code flow based on a child Node.
     *
     * @param {Node} node - The node to execute.
     * @param {?string} output - Expected output type. For example 'vec3'.
     * @return {Object} The code flow.
     */

Parameters:

  • node Node
  • output string

Returns: any

Calls:

  • node.build
Code
flowChildNode( node, output = null ) {

        const previousFlow = this.flow;

        const flow = {
            code: ''
        };

        this.flow = flow;

        flow.result = node.build( this, output );

        this.flow = previousFlow;

        return flow;

    }

NodeBuilder.flowNodeFromShaderStage(shaderStage: "compute" | "vertex" | "fragment" | "any", node: Node, output: string, propertyName: string): any

JSDoc:

/**
     * Executes a flow of code in a different stage.
     *
     * Some nodes like `varying()` have the ability to compute code in vertex-stage and
     * return the value in fragment-stage even if it is being executed in an input fragment.
     *
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @param {Node} node - The node to execute.
     * @param {?string} output - Expected output type. For example 'vec3'.
     * @param {?string} propertyName - The property name to assign the result.
     * @return {Object|Node|null} The code flow or node.build() result.
     */

Parameters:

  • shaderStage "compute" | "vertex" | "fragment" | "any"
  • node Node
  • output string
  • propertyName string

Returns: any

Calls:

  • this.setShaderStage
  • this.flowChildNode
  • node.build
Code
flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) {

        const previousTab = this.tab;
        const previousCache = this.cache;
        const previousShaderStage = this.shaderStage;
        const previousContext = this.context;

        this.setShaderStage( shaderStage );

        const context = { ...this.context };
        delete context.nodeBlock;

        this.cache = this.globalCache;
        this.tab = '\t';
        this.context = context;

        let result = null;

        if ( this.buildStage === 'generate' ) {

            const flowData = this.flowChildNode( node, output );

            if ( propertyName !== null ) {

                flowData.code += `${ this.tab + propertyName } = ${ flowData.result };\n`;

            }

            this.flowCode[ shaderStage ] = this.flowCode[ shaderStage ] + flowData.code;

            result = flowData;

        } else {

            result = node.build( this );

        }

        this.setShaderStage( previousShaderStage );

        this.cache = previousCache;
        this.tab = previousTab;
        this.context = previousContext;

        return result;

    }

NodeBuilder.getAttributesArray(): NodeAttribute[]

JSDoc:

/**
     * Returns an array holding all node attributes of this node builder.
     *
     * @return {Array<NodeAttribute>} The node attributes of this builder.
     */

Returns: NodeAttribute[]

Calls:

  • this.attributes.concat
Code
getAttributesArray() {

        return this.attributes.concat( this.bufferAttributes );

    }

NodeBuilder.getAttributes(): string

JSDoc:

/**
     * Returns the attribute definitions as a shader string for the given shader stage.
     *
     * @abstract
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The attribute code section.
     */

Returns: string

Calls:

  • console.warn
Code
getAttributes( /*shaderStage*/ ) {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.getVaryings(): string

JSDoc:

/**
     * Returns the varying definitions as a shader string for the given shader stage.
     *
     * @abstract
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The varying code section.
     */

Returns: string

Calls:

  • console.warn
Code
getVaryings( /*shaderStage*/ ) {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.getVar(type: string, name: string, count: number): string

JSDoc:

/**
     * Returns a single variable definition as a shader string for the given variable type and name.
     *
     * @param {string} type - The variable's type.
     * @param {string} name - The variable's name.
     * @param {?number} [count=null] - The array length.
     * @return {string} The shader string.
     */

Parameters:

  • type string
  • name string
  • count number

Returns: string

Calls:

  • this.generateArrayDeclaration
  • this.getType
Code
getVar( type, name, count = null ) {

        return `${ count !== null ? this.generateArrayDeclaration( type, count ) : this.getType( type ) } ${ name }`;

    }

NodeBuilder.getVars(shaderStage: "compute" | "vertex" | "fragment" | "any"): string

JSDoc:

/**
     * Returns the variable definitions as a shader string for the given shader stage.
     *
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The variable code section.
     */

Parameters:

  • shaderStage "compute" | "vertex" | "fragment" | "any"

Returns: string

Calls:

  • this.getVar
Code
getVars( shaderStage ) {

        let snippet = '';

        const vars = this.vars[ shaderStage ];

        if ( vars !== undefined ) {

            for ( const variable of vars ) {

                snippet += `${ this.getVar( variable.type, variable.name ) }; `;

            }

        }

        return snippet;

    }

NodeBuilder.getUniforms(): string

JSDoc:

/**
     * Returns the uniform definitions as a shader string for the given shader stage.
     *
     * @abstract
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The uniform code section.
     */

Returns: string

Calls:

  • console.warn
Code
getUniforms( /*shaderStage*/ ) {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.getCodes(shaderStage: "compute" | "vertex" | "fragment" | "any"): string

JSDoc:

/**
     * Returns the native code definitions as a shader string for the given shader stage.
     *
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The native code section.
     */

Parameters:

  • shaderStage "compute" | "vertex" | "fragment" | "any"

Returns: string

Code
getCodes( shaderStage ) {

        const codes = this.codes[ shaderStage ];

        let code = '';

        if ( codes !== undefined ) {

            for ( const nodeCode of codes ) {

                code += nodeCode.code + '\n';

            }

        }

        return code;

    }

NodeBuilder.getHash(): string

JSDoc:

/**
     * Returns the hash of this node builder.
     *
     * @return {string} The hash.
     */

Returns: string

Code
getHash() {

        return this.vertexShader + this.fragmentShader + this.computeShader;

    }

NodeBuilder.setShaderStage(shaderStage: "compute" | "vertex" | "fragment" | "any"): void

JSDoc:

/**
     * Sets the current shader stage.
     *
     * @param {?('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage to set.
     */

Parameters:

  • shaderStage "compute" | "vertex" | "fragment" | "any"

Returns: void

Code
setShaderStage( shaderStage ) {

        this.shaderStage = shaderStage;

    }

NodeBuilder.getShaderStage(): "compute" | "vertex" | "fragment" | "any"

JSDoc:

/**
     * Returns the current shader stage.
     *
     * @return {?('vertex'|'fragment'|'compute'|'any')} The current shader stage.
     */

Returns: "compute" | "vertex" | "fragment" | "any"

Code
getShaderStage() {

        return this.shaderStage;

    }

NodeBuilder.setBuildStage(buildStage: "setup" | "generate" | "analyze"): void

JSDoc:

/**
     * Sets the current build stage.
     *
     * @param {?('setup'|'analyze'|'generate')} buildStage - The build stage to set.
     */

Parameters:

  • buildStage "setup" | "generate" | "analyze"

Returns: void

Code
setBuildStage( buildStage ) {

        this.buildStage = buildStage;

    }

NodeBuilder.getBuildStage(): "setup" | "generate" | "analyze"

JSDoc:

/**
     * Returns the current build stage.
     *
     * @return {?('setup'|'analyze'|'generate')} The current build stage.
     */

Returns: "setup" | "generate" | "analyze"

Code
getBuildStage() {

        return this.buildStage;

    }

NodeBuilder.buildCode(): void

JSDoc:

/**
     * Controls the code build of the shader stages.
     *
     * @abstract
     */

Returns: void

Calls:

  • console.warn
Code
buildCode() {

        console.warn( 'Abstract function.' );

    }

NodeBuilder.addSubBuild(subBuild: SubBuildNode): void

JSDoc:

/**
     * Adds a sub-build layer to the node builder.
     *
     * @param {SubBuildNode} subBuild - The sub-build layer to add.
     */

Parameters:

  • subBuild SubBuildNode

Returns: void

Calls:

  • this.subBuildLayers.push
Code
addSubBuild( subBuild ) {

        this.subBuildLayers.push( subBuild );

    }

NodeBuilder.removeSubBuild(): SubBuildNode

JSDoc:

/**
     * Removes the last sub-build layer from the node builder.
     *
     * @return {SubBuildNode} The removed sub-build layer.
     */

Returns: SubBuildNode

Calls:

  • this.subBuildLayers.pop
Code
removeSubBuild() {

        return this.subBuildLayers.pop();

    }

NodeBuilder.getClosestSubBuild(data: any[] | Node | Set<any>): string

JSDoc:

/**
     * Returns the closest sub-build layer for the given data.
     *
     * @param {Node|Set|Array} data - The data to get the closest sub-build layer from.
     * @return {?string} The closest sub-build name or null if none found.
     */

Parameters:

  • data any[] | Node | Set<any>

Returns: string

Calls:

  • this.getDataFromNode
  • subBuildLayers.includes
Code
getClosestSubBuild( data ) {

        let subBuilds;

        if ( data && data.isNode ) {

            if ( data.isShaderCallNodeInternal ) {

                subBuilds = data.shaderNode.subBuilds;

            } else if ( data.isStackNode ) {

                subBuilds = [ data.subBuild ];

            } else {

                subBuilds = this.getDataFromNode( data, 'any' ).subBuilds;

            }

        } else if ( data instanceof Set ) {

            subBuilds = [ ...data ];

        } else {

            subBuilds = data;

        }

        if ( ! subBuilds ) return null;

        const subBuildLayers = this.subBuildLayers;

        for ( let i = subBuilds.length - 1; i >= 0; i -- ) {

            const subBuild = subBuilds[ i ];

            if ( subBuildLayers.includes( subBuild ) ) {

                return subBuild;

            }

        }

        return null;

    }

NodeBuilder.getSubBuildOutput(node: Node): string

JSDoc:

/**
     * Returns the output node of a sub-build layer.
     *
     * @param {Node} node - The node to get the output from.
     * @return {string} The output node name.
     */

Parameters:

  • node Node

Returns: string

Calls:

  • this.getSubBuildProperty
Code
getSubBuildOutput( node ) {

        return this.getSubBuildProperty( 'outputNode', node );

    }

NodeBuilder.getSubBuildProperty(property: string, node: Node): string

JSDoc:

/**
     * Returns the sub-build property name for the given property and node.
     *
     * @param {string} [property=''] - The property name.
     * @param {?Node} [node=null] - The node to get the sub-build from.
     * @return {string} The sub-build property name.
     */

Parameters:

  • property string
  • node Node

Returns: string

Calls:

  • this.getClosestSubBuild
Code
getSubBuildProperty( property = '', node = null ) {

        let subBuild;

        if ( node !== null ) {

            subBuild = this.getClosestSubBuild( node );

        } else {

            subBuild = this.subBuildFn;

        }

        let result;

        if ( subBuild ) {

            result = property ? ( subBuild + '_' + property ) : subBuild;

        } else {

            result = property;

        }

        return result;

    }

NodeBuilder.build(): NodeBuilder

JSDoc:

/**
     * Central build method which controls the build for the given object.
     *
     * @return {NodeBuilder} A reference to this node builder.
     */

Returns: NodeBuilder

Calls:

  • renderer.library.fromMaterial
  • console.error
  • nodeMaterial.build
  • this.addFlow
  • this.setBuildStage
  • this.flowNodeFromShaderStage
  • this.setShaderStage
  • this.flowNode
  • node.build
  • this.buildCode
  • this.buildUpdateNodes

Internal Comments:

// setup() -> stage 1: create possible new nodes and/or return an output reference node
// analyze()   -> stage 2: analyze nodes to possible optimization and validation
// generate()  -> stage 3: generate shader
// stage 4: build code for a specific output (x4)

Code
build() {

        const { object, material, renderer } = this;

        if ( material !== null ) {

            let nodeMaterial = renderer.library.fromMaterial( material );

            if ( nodeMaterial === null ) {

                console.error( `NodeMaterial: Material "${ material.type }" is not compatible.` );

                nodeMaterial = new NodeMaterial();

            }

            nodeMaterial.build( this );

        } else {

            this.addFlow( 'compute', object );

        }

        // setup() -> stage 1: create possible new nodes and/or return an output reference node
        // analyze()   -> stage 2: analyze nodes to possible optimization and validation
        // generate()  -> stage 3: generate shader

        for ( const buildStage of defaultBuildStages ) {

            this.setBuildStage( buildStage );

            if ( this.context.vertex && this.context.vertex.isNode ) {

                this.flowNodeFromShaderStage( 'vertex', this.context.vertex );

            }

            for ( const shaderStage of shaderStages ) {

                this.setShaderStage( shaderStage );

                const flowNodes = this.flowNodes[ shaderStage ];

                for ( const node of flowNodes ) {

                    if ( buildStage === 'generate' ) {

                        this.flowNode( node );

                    } else {

                        node.build( this );

                    }

                }

            }

        }

        this.setBuildStage( null );
        this.setShaderStage( null );

        // stage 4: build code for a specific output

        this.buildCode();
        this.buildUpdateNodes();

        return this;

    }

NodeBuilder.getNodeUniform(uniformNode: NodeUniform, type: string): Uniform

JSDoc:

/**
     * Returns a uniform representation which is later used for UBO generation and rendering.
     *
     * @param {NodeUniform} uniformNode - The uniform node.
     * @param {string} type - The requested type.
     * @return {Uniform} The uniform.
     */

Parameters:

  • uniformNode NodeUniform
  • type string

Returns: Uniform

Code
getNodeUniform( uniformNode, type ) {

        if ( type === 'float' || type === 'int' || type === 'uint' ) return new NumberNodeUniform( uniformNode );
        if ( type === 'vec2' || type === 'ivec2' || type === 'uvec2' ) return new Vector2NodeUniform( uniformNode );
        if ( type === 'vec3' || type === 'ivec3' || type === 'uvec3' ) return new Vector3NodeUniform( uniformNode );
        if ( type === 'vec4' || type === 'ivec4' || type === 'uvec4' ) return new Vector4NodeUniform( uniformNode );
        if ( type === 'color' ) return new ColorNodeUniform( uniformNode );
        if ( type === 'mat2' ) return new Matrix2NodeUniform( uniformNode );
        if ( type === 'mat3' ) return new Matrix3NodeUniform( uniformNode );
        if ( type === 'mat4' ) return new Matrix4NodeUniform( uniformNode );

        throw new Error( `Uniform "${type}" not declared.` );

    }

NodeBuilder.format(snippet: string, fromType: string, toType: string): string

JSDoc:

/**
     * Formats the given shader snippet from a given type into another one. E.g.
     * this method might be used to convert a simple float string `"1.0"` into a
     * `vec3` representation: `"vec3<f32>( 1.0 )"`.
     *
     * @param {string} snippet - The shader snippet.
     * @param {string} fromType - The source type.
     * @param {string} toType - The target type.
     * @return {string} The updated shader string.
     */

Parameters:

  • snippet string
  • fromType string
  • toType string

Returns: string

Calls:

  • this.getVectorType
  • this.isReference
  • this.getTypeLength
  • this.getType
  • 'xyz'.slice
  • this.format
  • this.getTypeFromLength
  • this.getComponentType

Internal Comments:

// @TODO: ignore for now (x2)
// convert a number value to vector type, e.g: (x3)
// vec3( 1u ) -> vec3( float( 1u ) ) (x3)

Code
format( snippet, fromType, toType ) {

        fromType = this.getVectorType( fromType );
        toType = this.getVectorType( toType );

        if ( fromType === toType || toType === null || this.isReference( toType ) ) {

            return snippet;

        }

        const fromTypeLength = this.getTypeLength( fromType );
        const toTypeLength = this.getTypeLength( toType );

        if ( fromTypeLength === 16 && toTypeLength === 9 ) {

            return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xyz, ${ snippet }[ 1 ].xyz, ${ snippet }[ 2 ].xyz )`;

        }

        if ( fromTypeLength === 9 && toTypeLength === 4 ) {

            return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xy, ${ snippet }[ 1 ].xy )`;

        }


        if ( fromTypeLength > 4 ) { // fromType is matrix-like

            // @TODO: ignore for now

            return snippet;

        }

        if ( toTypeLength > 4 || toTypeLength === 0 ) { // toType is matrix-like or unknown

            // @TODO: ignore for now

            return snippet;

        }

        if ( fromTypeLength === toTypeLength ) {

            return `${ this.getType( toType ) }( ${ snippet } )`;

        }

        if ( fromTypeLength > toTypeLength ) {

            snippet = toType === 'bool' ? `all( ${ snippet } )` : `${ snippet }.${ 'xyz'.slice( 0, toTypeLength ) }`;

            return this.format( snippet, this.getTypeFromLength( toTypeLength, this.getComponentType( fromType ) ), toType );

        }

        if ( toTypeLength === 4 && fromTypeLength > 1 ) { // toType is vec4-like

            return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec3' ) }, 1.0 )`;

        }

        if ( fromTypeLength === 2 ) { // fromType is vec2-like and toType is vec3-like

            return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec2' ) }, 0.0 )`;

        }

        if ( fromTypeLength === 1 && toTypeLength > 1 && fromType !== this.getComponentType( toType ) ) { // fromType is float-like

            // convert a number value to vector type, e.g:
            // vec3( 1u ) -> vec3( float( 1u ) )

            snippet = `${ this.getType( this.getComponentType( toType ) ) }( ${ snippet } )`;

        }

        return `${ this.getType( toType ) }( ${ snippet } )`; // fromType is float-like

    }

NodeBuilder.getSignature(): string

JSDoc:

/**
     * Returns a signature with the engine's current revision.
     *
     * @return {string} The signature.
     */

Returns: string

Code
getSignature() {

        return `// Three.js r${ REVISION } - Node System\n`;

    }

generateConst(value: any): string

Parameters:

  • value any

Returns: string

Calls:

  • this.generateConst
Code
value => this.generateConst( componentType, value )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

next(): { value: any; done: boolean; }

Returns: { value: any; done: boolean; }

Code
() => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )

Classes

NodeBuilder

Class Code
class NodeBuilder {

    /**
     * Constructs a new node builder.
     *
     * @param {Object3D} object - The 3D object.
     * @param {Renderer} renderer - The current renderer.
     * @param {NodeParser} parser - A reference to a node parser.
     */
    constructor( object, renderer, parser ) {

        /**
         * The 3D object.
         *
         * @type {Object3D}
         */
        this.object = object;

        /**
         * The material of the 3D object.
         *
         * @type {?Material}
         */
        this.material = ( object && object.material ) || null;

        /**
         * The geometry of the 3D object.
         *
         * @type {?BufferGeometry}
         */
        this.geometry = ( object && object.geometry ) || null;

        /**
         * The current renderer.
         *
         * @type {Renderer}
         */
        this.renderer = renderer;

        /**
         * A reference to a node parser.
         *
         * @type {NodeParser}
         */
        this.parser = parser;

        /**
         * The scene the 3D object belongs to.
         *
         * @type {?Scene}
         * @default null
         */
        this.scene = null;

        /**
         * The camera the 3D object is rendered with.
         *
         * @type {?Camera}
         * @default null
         */
        this.camera = null;

        /**
         * A list of all nodes the builder is processing
         * for this 3D object.
         *
         * @type {Array<Node>}
         */
        this.nodes = [];

        /**
         * A list of all sequential nodes.
         *
         * @type {Array<Node>}
         */
        this.sequentialNodes = [];

        /**
         * A list of all nodes which {@link Node#update} method should be executed.
         *
         * @type {Array<Node>}
         */
        this.updateNodes = [];

        /**
         * A list of all nodes which {@link Node#updateBefore} method should be executed.
         *
         * @type {Array<Node>}
         */
        this.updateBeforeNodes = [];

        /**
         * A list of all nodes which {@link Node#updateAfter} method should be executed.
         *
         * @type {Array<Node>}
         */
        this.updateAfterNodes = [];

        /**
         * A dictionary that assigns each node to a unique hash.
         *
         * @type {Object<number,Node>}
         */
        this.hashNodes = {};

        /**
         * A reference to a node material observer.
         *
         * @type {?NodeMaterialObserver}
         * @default null
         */
        this.observer = null;

        /**
         * A reference to the current lights node.
         *
         * @type {?LightsNode}
         * @default null
         */
        this.lightsNode = null;

        /**
         * A reference to the current environment node.
         *
         * @type {?Node}
         * @default null
         */
        this.environmentNode = null;

        /**
         * A reference to the current fog node.
         *
         * @type {?Node}
         * @default null
         */
        this.fogNode = null;

        /**
         * The current clipping context.
         *
         * @type {?ClippingContext}
         */
        this.clippingContext = null;

        /**
         * The generated vertex shader.
         *
         * @type {?string}
         */
        this.vertexShader = null;

        /**
         * The generated fragment shader.
         *
         * @type {?string}
         */
        this.fragmentShader = null;

        /**
         * The generated compute shader.
         *
         * @type {?string}
         */
        this.computeShader = null;

        /**
         * Nodes used in the primary flow of code generation.
         *
         * @type {Object<string,Array<Node>>}
         */
        this.flowNodes = { vertex: [], fragment: [], compute: [] };

        /**
         * Nodes code from `.flowNodes`.
         *
         * @type {Object<string,string>}
         */
        this.flowCode = { vertex: '', fragment: '', compute: '' };

        /**
         * This dictionary holds the node uniforms of the builder.
         * The uniforms are maintained in an array for each shader stage.
         *
         * @type {Object}
         */
        this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 };

        /**
         * This dictionary holds the output structs of the builder.
         * The structs are maintained in an array for each shader stage.
         *
         * @type {Object}
         */
        this.structs = { vertex: [], fragment: [], compute: [], index: 0 };

        /**
         * This dictionary holds the bindings for each shader stage.
         *
         * @type {Object}
         */
        this.bindings = { vertex: {}, fragment: {}, compute: {} };

        /**
         * This dictionary maintains the binding indices per bind group.
         *
         * @type {Object}
         */
        this.bindingsIndexes = {};

        /**
         * Reference to the array of bind groups.
         *
         * @type {?Array<BindGroup>}
         */
        this.bindGroups = null;

        /**
         * This array holds the node attributes of this builder
         * created via {@link AttributeNode}.
         *
         * @type {Array<NodeAttribute>}
         */
        this.attributes = [];

        /**
         * This array holds the node attributes of this builder
         * created via {@link BufferAttributeNode}.
         *
         * @type {Array<NodeAttribute>}
         */
        this.bufferAttributes = [];

        /**
         * This array holds the node varyings of this builder.
         *
         * @type {Array<NodeVarying>}
         */
        this.varyings = [];

        /**
         * This dictionary holds the (native) node codes of this builder.
         * The codes are maintained in an array for each shader stage.
         *
         * @type {Object<string,Array<NodeCode>>}
         */
        this.codes = {};

        /**
         * This dictionary holds the node variables of this builder.
         * The variables are maintained in an array for each shader stage.
         * This dictionary is also used to count the number of variables
         * according to their type (const, vars).
         *
         * @type {Object<string,Array<NodeVar>|number>}
         */
        this.vars = {};

        /**
         * This dictionary holds the declarations for each shader stage.
         *
         * @type {Object}
         */
        this.declarations = {};

        /**
         * Current code flow.
         * All code generated in this stack will be stored in `.flow`.
         *
         * @type {{code: string}}
         */
        this.flow = { code: '' };

        /**
         * A chain of nodes.
         * Used to check recursive calls in node-graph.
         *
         * @type {Array<Node>}
         */
        this.chaining = [];

        /**
         * The current stack.
         * This reflects the current process in the code block hierarchy,
         * it is useful to know if the current process is inside a conditional for example.
         *
         * @type {StackNode}
         */
        this.stack = stack();

        /**
         * List of stack nodes.
         * The current stack hierarchy is stored in an array.
         *
         * @type {Array<StackNode>}
         */
        this.stacks = [];

        /**
         * A tab value. Used for shader string generation.
         *
         * @type {string}
         * @default '\t'
         */
        this.tab = '\t';

        /**
         * Reference to the current function node.
         *
         * @type {?FunctionNode}
         * @default null
         */
        this.currentFunctionNode = null;

        /**
         * The builder's context.
         *
         * @type {Object}
         */
        this.context = {
            material: this.material
        };

        /**
         * The builder's cache.
         *
         * @type {NodeCache}
         */
        this.cache = new NodeCache();

        /**
         * Since the {@link NodeBuilder#cache} might be temporarily
         * overwritten by other caches, this member retains the reference
         * to the builder's own cache.
         *
         * @type {NodeCache}
         * @default this.cache
         */
        this.globalCache = this.cache;

        this.flowsData = new WeakMap();

        /**
         * The current shader stage.
         *
         * @type {?('vertex'|'fragment'|'compute'|'any')}
         */
        this.shaderStage = null;

        /**
         * The current build stage.
         *
         * @type {?('setup'|'analyze'|'generate')}
         */
        this.buildStage = null;

        /**
         * The sub-build layers.
         *
         * @type {Array<SubBuildNode>}
         * @default []
         */
        this.subBuildLayers = [];

        /**
         * The current stack of nodes.
         *
         * @type {?StackNode}
         * @default null
         */
        this.currentStack = null;

        /**
         * The current sub-build TSL function(Fn).
         *
         * @type {?string}
         * @default null
         */
        this.subBuildFn = null;

    }

    /**
     * Returns the bind groups of the current renderer.
     *
     * @return {ChainMap} The cache.
     */
    getBindGroupsCache() {

        let bindGroupsCache = rendererCache.get( this.renderer );

        if ( bindGroupsCache === undefined ) {

            bindGroupsCache = new ChainMap();

            rendererCache.set( this.renderer, bindGroupsCache );

        }

        return bindGroupsCache;

    }

    /**
     * Factory method for creating an instance of {@link RenderTarget} with the given
     * dimensions and options.
     *
     * @param {number} width - The width of the render target.
     * @param {number} height - The height of the render target.
     * @param {Object} options - The options of the render target.
     * @return {RenderTarget} The render target.
     */
    createRenderTarget( width, height, options ) {

        return new RenderTarget( width, height, options );

    }

    /**
     * Factory method for creating an instance of {@link CubeRenderTarget} with the given
     * dimensions and options.
     *
     * @param {number} size - The size of the cube render target.
     * @param {Object} options - The options of the cube render target.
     * @return {CubeRenderTarget} The cube render target.
     */
    createCubeRenderTarget( size, options ) {

        return new CubeRenderTarget( size, options );

    }

    /**
     * Whether the given node is included in the internal array of nodes or not.
     *
     * @param {Node} node - The node to test.
     * @return {boolean} Whether the given node is included in the internal array of nodes or not.
     */
    includes( node ) {

        return this.nodes.includes( node );

    }

    /**
     * Returns the output struct name which is required by
     * {@link OutputStructNode}.
     *
     * @abstract
     * @return {string} The name of the output struct.
     */
    getOutputStructName() {}

    /**
     * Returns a bind group for the given group name and binding.
     *
     * @private
     * @param {string} groupName - The group name.
     * @param {Array<NodeUniformsGroup>} bindings - List of bindings.
     * @return {BindGroup} The bind group
     */
    _getBindGroup( groupName, bindings ) {

        const bindGroupsCache = this.getBindGroupsCache();

        //

        const bindingsArray = [];

        let sharedGroup = true;

        for ( const binding of bindings ) {

            bindingsArray.push( binding );

            sharedGroup = sharedGroup && binding.groupNode.shared !== true;

        }

        //

        let bindGroup;

        if ( sharedGroup ) {

            bindGroup = bindGroupsCache.get( bindingsArray );

            if ( bindGroup === undefined ) {

                bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );

                bindGroupsCache.set( bindingsArray, bindGroup );

            }

        } else {

            bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );

        }

        return bindGroup;

    }

    /**
     * Returns an array of node uniform groups for the given group name and shader stage.
     *
     * @param {string} groupName - The group name.
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {Array<NodeUniformsGroup>} The array of node uniform groups.
     */
    getBindGroupArray( groupName, shaderStage ) {

        const bindings = this.bindings[ shaderStage ];

        let bindGroup = bindings[ groupName ];

        if ( bindGroup === undefined ) {

            if ( this.bindingsIndexes[ groupName ] === undefined ) {

                this.bindingsIndexes[ groupName ] = { binding: 0, group: Object.keys( this.bindingsIndexes ).length };

            }

            bindings[ groupName ] = bindGroup = [];

        }

        return bindGroup;

    }

    /**
     * Returns a list bindings of all shader stages separated by groups.
     *
     * @return {Array<BindGroup>} The list of bindings.
     */
    getBindings() {

        let bindingsGroups = this.bindGroups;

        if ( bindingsGroups === null ) {

            const groups = {};
            const bindings = this.bindings;

            for ( const shaderStage of shaderStages ) {

                for ( const groupName in bindings[ shaderStage ] ) {

                    const uniforms = bindings[ shaderStage ][ groupName ];

                    const groupUniforms = groups[ groupName ] || ( groups[ groupName ] = [] );
                    groupUniforms.push( ...uniforms );

                }

            }

            bindingsGroups = [];

            for ( const groupName in groups ) {

                const group = groups[ groupName ];

                const bindingsGroup = this._getBindGroup( groupName, group );

                bindingsGroups.push( bindingsGroup );

            }

            this.bindGroups = bindingsGroups;

        }

        return bindingsGroups;

    }

    /**
     * Sorts the bind groups and updates {@link NodeBuilder#bindingsIndexes}.
     */
    sortBindingGroups() {

        const bindingsGroups = this.getBindings();

        bindingsGroups.sort( ( a, b ) => ( a.bindings[ 0 ].groupNode.order - b.bindings[ 0 ].groupNode.order ) );

        for ( let i = 0; i < bindingsGroups.length; i ++ ) {

            const bindingGroup = bindingsGroups[ i ];
            this.bindingsIndexes[ bindingGroup.name ].group = i;

            bindingGroup.index = i;

        }

    }

    /**
     * The builder maintains each node in a hash-based dictionary.
     * This method sets the given node (value) with the given hash (key) into this dictionary.
     *
     * @param {Node} node - The node to add.
     * @param {number} hash - The hash of the node.
     */
    setHashNode( node, hash ) {

        this.hashNodes[ hash ] = node;

    }

    /**
     * Adds a node to this builder.
     *
     * @param {Node} node - The node to add.
     */
    addNode( node ) {

        if ( this.nodes.includes( node ) === false ) {

            this.nodes.push( node );

            this.setHashNode( node, node.getHash( this ) );

        }

    }

    /**
     * It is used to add Nodes that will be used as FRAME and RENDER events,
     * and need to follow a certain sequence in the calls to work correctly.
     * This function should be called after 'setup()' in the 'build()' process to ensure that the child nodes are processed first.
     *
     * @param {Node} node - The node to add.
     */
    addSequentialNode( node ) {

        if ( this.sequentialNodes.includes( node ) === false ) {

            this.sequentialNodes.push( node );

        }

    }

    /**
     * Checks the update types of nodes
     */
    buildUpdateNodes() {

        for ( const node of this.nodes ) {

            const updateType = node.getUpdateType();

            if ( updateType !== NodeUpdateType.NONE ) {

                this.updateNodes.push( node.getSelf() );

            }

        }

        for ( const node of this.sequentialNodes ) {

            const updateBeforeType = node.getUpdateBeforeType();
            const updateAfterType = node.getUpdateAfterType();

            if ( updateBeforeType !== NodeUpdateType.NONE ) {

                this.updateBeforeNodes.push( node.getSelf() );

            }

            if ( updateAfterType !== NodeUpdateType.NONE ) {

                this.updateAfterNodes.push( node.getSelf() );

            }

        }

    }

    /**
     * A reference the current node which is the
     * last node in the chain of nodes.
     *
     * @type {Node}
     */
    get currentNode() {

        return this.chaining[ this.chaining.length - 1 ];

    }

    /**
     * Whether the given texture is filtered or not.
     *
     * @param {Texture} texture - The texture to check.
     * @return {boolean} Whether the given texture is filtered or not.
     */
    isFilteredTexture( texture ) {

        return ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter ||
            texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter );

    }

    /**
     * Adds the given node to the internal node chain.
     * This is used to check recursive calls in node-graph.
     *
     * @param {Node} node - The node to add.
     */
    addChain( node ) {

        /*
        if ( this.chaining.indexOf( node ) !== - 1 ) {

            console.warn( 'Recursive node: ', node );

        }
        */

        this.chaining.push( node );

    }

    /**
     * Removes the given node from the internal node chain.
     *
     * @param {Node} node - The node to remove.
     */
    removeChain( node ) {

        const lastChain = this.chaining.pop();

        if ( lastChain !== node ) {

            throw new Error( 'NodeBuilder: Invalid node chaining!' );

        }

    }

    /**
     * Returns the native shader method name for a given generic name. E.g.
     * the method name `textureDimensions` matches the WGSL name but must be
     * resolved to `textureSize` in GLSL.
     *
     * @abstract
     * @param {string} method - The method name to resolve.
     * @return {string} The resolved method name.
     */
    getMethod( method ) {

        return method;

    }

    /**
     * Returns the native snippet for a ternary operation. E.g. GLSL would output
     * a ternary op as `cond ? x : y` whereas WGSL would output it as `select(y, x, cond)`
     *
     * @abstract
     * @param {string} condSnippet - The condition determining which expression gets resolved.
     * @param {string} ifSnippet - The expression to resolve to if the condition is true.
     * @param {string} elseSnippet - The expression to resolve to if the condition is false.
     * @return {string} The resolved method name.
     */
    getTernary( /* condSnippet, ifSnippet, elseSnippet*/ ) {

        return null;

    }

    /**
     * Returns a node for the given hash, see {@link NodeBuilder#setHashNode}.
     *
     * @param {number} hash - The hash of the node.
     * @return {Node} The found node.
     */
    getNodeFromHash( hash ) {

        return this.hashNodes[ hash ];

    }

    /**
     * Adds the Node to a target flow so that it can generate code in the 'generate' process.
     *
     * @param {('vertex'|'fragment'|'compute')} shaderStage - The shader stage.
     * @param {Node} node - The node to add.
     * @return {Node} The node.
     */
    addFlow( shaderStage, node ) {

        this.flowNodes[ shaderStage ].push( node );

        return node;

    }

    /**
     * Sets builder's context.
     *
     * @param {Object} context - The context to set.
     */
    setContext( context ) {

        this.context = context;

    }

    /**
     * Returns the builder's current context.
     *
     * @return {Object} The builder's current context.
     */
    getContext() {

        return this.context;

    }

    /**
     * Gets a context used in shader construction that can be shared across different materials.
     * This is necessary since the renderer cache can reuse shaders generated in one material and use them in another.
     *
     * @return {Object} The builder's current context without material.
     */
    getSharedContext() {

        const context = { ...this.context };

        delete context.material;

        return this.context;

    }

    /**
     * Sets builder's cache.
     *
     * @param {NodeCache} cache - The cache to set.
     */
    setCache( cache ) {

        this.cache = cache;

    }

    /**
     * Returns the builder's current cache.
     *
     * @return {NodeCache} The builder's current cache.
     */
    getCache() {

        return this.cache;

    }

    /**
     * Returns a cache for the given node.
     *
     * @param {Node} node - The node.
     * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not.
     * @return {NodeCache} The cache.
     */
    getCacheFromNode( node, parent = true ) {

        const data = this.getDataFromNode( node );
        if ( data.cache === undefined ) data.cache = new NodeCache( parent ? this.getCache() : null );

        return data.cache;

    }

    /**
     * Whether the requested feature is available or not.
     *
     * @abstract
     * @param {string} name - The requested feature.
     * @return {boolean} Whether the requested feature is supported or not.
     */
    isAvailable( /*name*/ ) {

        return false;

    }

    /**
     * Returns the vertexIndex input variable as a native shader string.
     *
     * @abstract
     * @return {string} The instanceIndex shader string.
     */
    getVertexIndex() {

        console.warn( 'Abstract function.' );

    }

    /**
     * Returns the instanceIndex input variable as a native shader string.
     *
     * @abstract
     * @return {string} The instanceIndex shader string.
     */
    getInstanceIndex() {

        console.warn( 'Abstract function.' );

    }

    /**
     * Returns the drawIndex input variable as a native shader string.
     * Only relevant for WebGL and its `WEBGL_multi_draw` extension.
     *
     * @abstract
     * @return {?string} The drawIndex shader string.
     */
    getDrawIndex() {

        console.warn( 'Abstract function.' );

    }

    /**
     * Returns the frontFacing input variable as a native shader string.
     *
     * @abstract
     * @return {string} The frontFacing shader string.
     */
    getFrontFacing() {

        console.warn( 'Abstract function.' );

    }

    /**
     * Returns the fragCoord input variable as a native shader string.
     *
     * @abstract
     * @return {string} The fragCoord shader string.
     */
    getFragCoord() {

        console.warn( 'Abstract function.' );

    }

    /**
     * Whether to flip texture data along its vertical axis or not. WebGL needs
     * this method evaluate to `true`, WebGPU to `false`.
     *
     * @abstract
     * @return {boolean} Whether to flip texture data along its vertical axis or not.
     */
    isFlipY() {

        return false;

    }

    /**
     * Calling this method increases the usage count for the given node by one.
     *
     * @param {Node} node - The node to increase the usage count for.
     * @return {number} The updated usage count.
     */
    increaseUsage( node ) {

        const nodeData = this.getDataFromNode( node );
        nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1;

        return nodeData.usageCount;

    }

    /**
     * Generates a texture sample shader string for the given texture data.
     *
     * @abstract
     * @param {Texture} texture - The texture.
     * @param {string} textureProperty - The texture property name.
     * @param {string} uvSnippet - Snippet defining the texture coordinates.
     * @return {string} The generated shader string.
     */
    generateTexture( /* texture, textureProperty, uvSnippet */ ) {

        console.warn( 'Abstract function.' );

    }

    /**
     * Generates a texture LOD shader string for the given texture data.
     *
     * @abstract
     * @param {Texture} texture - The texture.
     * @param {string} textureProperty - The texture property name.
     * @param {string} uvSnippet - Snippet defining the texture coordinates.
     * @param {?string} depthSnippet - Snippet defining the 0-based texture array index to sample.
     * @param {string} levelSnippet - Snippet defining the mip level.
     * @return {string} The generated shader string.
     */
    generateTextureLod( /* texture, textureProperty, uvSnippet, depthSnippet, levelSnippet */ ) {

        console.warn( 'Abstract function.' );

    }

    /**
     * Generates the array declaration string.
     *
     * @param {string} type - The type.
     * @param {?number} [count] - The count.
     * @return {string} The generated value as a shader string.
     */
    generateArrayDeclaration( type, count ) {

        return this.getType( type ) + '[ ' + count + ' ]';

    }

    /**
     * Generates the array shader string for the given type and value.
     *
     * @param {string} type - The type.
     * @param {?number} [count] - The count.
     * @param {?Array<Node>} [values=null] - The default values.
     * @return {string} The generated value as a shader string.
     */
    generateArray( type, count, values = null ) {

        let snippet = this.generateArrayDeclaration( type, count ) + '( ';

        for ( let i = 0; i < count; i ++ ) {

            const value = values ? values[ i ] : null;

            if ( value !== null ) {

                snippet += value.build( this, type );

            } else {

                snippet += this.generateConst( type );

            }

            if ( i < count - 1 ) snippet += ', ';

        }

        snippet += ' )';

        return snippet;

    }

    /**
     * Generates the struct shader string.
     *
     * @param {string} type - The type.
     * @param {Array<Object>} [membersLayout] - The count.
     * @param {?Array<Node>} [values=null] - The default values.
     * @return {string} The generated value as a shader string.
     */
    generateStruct( type, membersLayout, values = null ) {

        const snippets = [];

        for ( const member of membersLayout ) {

            const { name, type } = member;

            if ( values && values[ name ] && values[ name ].isNode ) {

                snippets.push( values[ name ].build( this, type ) );

            } else {

                snippets.push( this.generateConst( type ) );

            }

        }

        return type + '( ' + snippets.join( ', ' ) + ' )';

    }

    /**
     * Generates the shader string for the given type and value.
     *
     * @param {string} type - The type.
     * @param {?any} [value=null] - The value.
     * @return {string} The generated value as a shader string.
     */
    generateConst( type, value = null ) {

        if ( value === null ) {

            if ( type === 'float' || type === 'int' || type === 'uint' ) value = 0;
            else if ( type === 'bool' ) value = false;
            else if ( type === 'color' ) value = new Color();
            else if ( type === 'vec2' ) value = new Vector2();
            else if ( type === 'vec3' ) value = new Vector3();
            else if ( type === 'vec4' ) value = new Vector4();

        }

        if ( type === 'float' ) return toFloat( value );
        if ( type === 'int' ) return `${ Math.round( value ) }`;
        if ( type === 'uint' ) return value >= 0 ? `${ Math.round( value ) }u` : '0u';
        if ( type === 'bool' ) return value ? 'true' : 'false';
        if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ toFloat( value.r ) }, ${ toFloat( value.g ) }, ${ toFloat( value.b ) } )`;

        const typeLength = this.getTypeLength( type );

        const componentType = this.getComponentType( type );

        const generateConst = value => this.generateConst( componentType, value );

        if ( typeLength === 2 ) {

            return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) } )`;

        } else if ( typeLength === 3 ) {

            return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) } )`;

        } else if ( typeLength === 4 && type !== 'mat2' ) {

            return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) }, ${ generateConst( value.w ) } )`;

        } else if ( typeLength >= 4 && value && ( value.isMatrix2 || value.isMatrix3 || value.isMatrix4 ) ) {

            return `${ this.getType( type ) }( ${ value.elements.map( generateConst ).join( ', ' ) } )`;

        } else if ( typeLength > 4 ) {

            return `${ this.getType( type ) }()`;

        }

        throw new Error( `NodeBuilder: Type '${type}' not found in generate constant attempt.` );

    }

    /**
     * It might be necessary to convert certain data types to different ones
     * so this method can be used to hide the conversion.
     *
     * @param {string} type - The type.
     * @return {string} The updated type.
     */
    getType( type ) {

        if ( type === 'color' ) return 'vec3';

        return type;

    }

    /**
     * Whether the given attribute name is defined in the geometry or not.
     *
     * @param {string} name - The attribute name.
     * @return {boolean} Whether the given attribute name is defined in the geometry.
     */
    hasGeometryAttribute( name ) {

        return this.geometry && this.geometry.getAttribute( name ) !== undefined;

    }

    /**
     * Returns a node attribute for the given name and type.
     *
     * @param {string} name - The attribute's name.
     * @param {string} type - The attribute's type.
     * @return {NodeAttribute} The node attribute.
     */
    getAttribute( name, type ) {

        const attributes = this.attributes;

        // find attribute

        for ( const attribute of attributes ) {

            if ( attribute.name === name ) {

                return attribute;

            }

        }

        // create a new if no exist

        const attribute = new NodeAttribute( name, type );

        this.registerDeclaration( attribute );

        attributes.push( attribute );

        return attribute;

    }

    /**
     * Returns for the given node and shader stage the property name for the shader.
     *
     * @param {Node} node - The node.
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The property name.
     */
    getPropertyName( node/*, shaderStage*/ ) {

        return node.name;

    }

    /**
     * Whether the given type is a vector type or not.
     *
     * @param {string} type - The type to check.
     * @return {boolean} Whether the given type is a vector type or not.
     */
    isVector( type ) {

        return /vec\d/.test( type );

    }

    /**
     * Whether the given type is a matrix type or not.
     *
     * @param {string} type - The type to check.
     * @return {boolean} Whether the given type is a matrix type or not.
     */
    isMatrix( type ) {

        return /mat\d/.test( type );

    }

    /**
     * Whether the given type is a reference type or not.
     *
     * @param {string} type - The type to check.
     * @return {boolean} Whether the given type is a reference type or not.
     */
    isReference( type ) {

        return type === 'void' || type === 'property' || type === 'sampler' || type === 'samplerComparison' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'depthTexture' || type === 'texture3D';

    }

    /**
     * Checks if the given texture requires a manual conversion to the working color space.
     *
     * @abstract
     * @param {Texture} texture - The texture to check.
     * @return {boolean} Whether the given texture requires a conversion to working color space or not.
     */
    needsToWorkingColorSpace( /*texture*/ ) {

        return false;

    }

    /**
     * Returns the component type of a given texture.
     *
     * @param {Texture} texture - The texture.
     * @return {string} The component type.
     */
    getComponentTypeFromTexture( texture ) {

        const type = texture.type;

        if ( texture.isDataTexture ) {

            if ( type === IntType ) return 'int';
            if ( type === UnsignedIntType ) return 'uint';

        }

        return 'float';

    }

    /**
     * Returns the element type for a given type.
     *
     * @param {string} type - The type.
     * @return {string} The element type.
     */
    getElementType( type ) {

        if ( type === 'mat2' ) return 'vec2';
        if ( type === 'mat3' ) return 'vec3';
        if ( type === 'mat4' ) return 'vec4';

        return this.getComponentType( type );

    }

    /**
     * Returns the component type for a given type.
     *
     * @param {string} type - The type.
     * @return {string} The component type.
     */
    getComponentType( type ) {

        type = this.getVectorType( type );

        if ( type === 'float' || type === 'bool' || type === 'int' || type === 'uint' ) return type;

        const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec( type );

        if ( componentType === null ) return null;

        if ( componentType[ 1 ] === 'b' ) return 'bool';
        if ( componentType[ 1 ] === 'i' ) return 'int';
        if ( componentType[ 1 ] === 'u' ) return 'uint';

        return 'float';

    }

    /**
     * Returns the vector type for a given type.
     *
     * @param {string} type - The type.
     * @return {string} The vector type.
     */
    getVectorType( type ) {

        if ( type === 'color' ) return 'vec3';
        if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) return 'vec4';

        return type;

    }

    /**
     * Returns the data type for the given the length and component type.
     *
     * @param {number} length - The length.
     * @param {string} [componentType='float'] - The component type.
     * @return {string} The type.
     */
    getTypeFromLength( length, componentType = 'float' ) {

        if ( length === 1 ) return componentType;

        let baseType = getTypeFromLength( length );
        const prefix = componentType === 'float' ? '' : componentType[ 0 ];

        // fix edge case for mat2x2 being same size as vec4
        if ( /mat2/.test( componentType ) === true ) {

            baseType = baseType.replace( 'vec', 'mat' );

        }

        return prefix + baseType;

    }

    /**
     * Returns the type for a given typed array.
     *
     * @param {TypedArray} array - The typed array.
     * @return {string} The type.
     */
    getTypeFromArray( array ) {

        return typeFromArray.get( array.constructor );

    }

    /**
     * Returns the type is an integer type.
     *
     * @param {string} type - The type.
     * @return {boolean} Whether the type is an integer type or not.
     */
    isInteger( type ) {

        return /int|uint|(i|u)vec/.test( type );

    }

    /**
     * Returns the type for a given buffer attribute.
     *
     * @param {BufferAttribute} attribute - The buffer attribute.
     * @return {string} The type.
     */
    getTypeFromAttribute( attribute ) {

        let dataAttribute = attribute;

        if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data;

        const array = dataAttribute.array;
        const itemSize = attribute.itemSize;
        const normalized = attribute.normalized;

        let arrayType;

        if ( ! ( attribute instanceof Float16BufferAttribute ) && normalized !== true ) {

            arrayType = this.getTypeFromArray( array );

        }

        return this.getTypeFromLength( itemSize, arrayType );

    }

    /**
     * Returns the length for the given data type.
     *
     * @param {string} type - The data type.
     * @return {number} The length.
     */
    getTypeLength( type ) {

        const vecType = this.getVectorType( type );
        const vecNum = /vec([2-4])/.exec( vecType );

        if ( vecNum !== null ) return Number( vecNum[ 1 ] );
        if ( vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint' ) return 1;
        if ( /mat2/.test( type ) === true ) return 4;
        if ( /mat3/.test( type ) === true ) return 9;
        if ( /mat4/.test( type ) === true ) return 16;

        return 0;

    }

    /**
     * Returns the vector type for a given matrix type.
     *
     * @param {string} type - The matrix type.
     * @return {string} The vector type.
     */
    getVectorFromMatrix( type ) {

        return type.replace( 'mat', 'vec' );

    }

    /**
     * For a given type this method changes the component type to the
     * given value. E.g. `vec4` should be changed to the new component type
     * `uint` which results in `uvec4`.
     *
     * @param {string} type - The type.
     * @param {string} newComponentType - The new component type.
     * @return {string} The new type.
     */
    changeComponentType( type, newComponentType ) {

        return this.getTypeFromLength( this.getTypeLength( type ), newComponentType );

    }

    /**
     * Returns the integer type pendant for the given type.
     *
     * @param {string} type - The type.
     * @return {string} The integer type.
     */
    getIntegerType( type ) {

        const componentType = this.getComponentType( type );

        if ( componentType === 'int' || componentType === 'uint' ) return type;

        return this.changeComponentType( type, 'int' );

    }

    /**
     * Adds a stack node to the internal stack.
     *
     * @return {StackNode} The added stack node.
     */
    addStack() {

        this.stack = stack( this.stack );

        this.stacks.push( getCurrentStack() || this.stack );
        setCurrentStack( this.stack );

        return this.stack;

    }

    /**
     * Removes the last stack node from the internal stack.
     *
     * @return {StackNode} The removed stack node.
     */
    removeStack() {

        const lastStack = this.stack;
        this.stack = lastStack.parent;

        setCurrentStack( this.stacks.pop() );

        return lastStack;

    }

    /**
     * The builder maintains (cached) data for each node during the building process. This method
     * can be used to get these data for a specific shader stage and cache.
     *
     * @param {Node} node - The node to get the data for.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @param {?NodeCache} cache - An optional cache.
     * @return {Object} The node data.
     */
    getDataFromNode( node, shaderStage = this.shaderStage, cache = null ) {

        cache = cache === null ? ( node.isGlobal( this ) ? this.globalCache : this.cache ) : cache;

        let nodeData = cache.getData( node );

        if ( nodeData === undefined ) {

            nodeData = {};

            cache.setData( node, nodeData );

        }

        if ( nodeData[ shaderStage ] === undefined ) nodeData[ shaderStage ] = {};

        //

        let data = nodeData[ shaderStage ];

        const subBuilds = nodeData.any ? nodeData.any.subBuilds : null;
        const subBuild = this.getClosestSubBuild( subBuilds );

        if ( subBuild ) {

            if ( data.subBuildsCache === undefined ) data.subBuildsCache = {};

            data = data.subBuildsCache[ subBuild ] || ( data.subBuildsCache[ subBuild ] = {} );
            data.subBuilds = subBuilds;

        }

        return data;

    }

    /**
     * Returns the properties for the given node and shader stage.
     *
     * @param {Node} node - The node to get the properties for.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage='any'] - The shader stage.
     * @return {Object} The node properties.
     */
    getNodeProperties( node, shaderStage = 'any' ) {

        const nodeData = this.getDataFromNode( node, shaderStage );

        return nodeData.properties || ( nodeData.properties = { outputNode: null } );

    }

    /**
     * Returns an instance of {@link NodeAttribute} for the given buffer attribute node.
     *
     * @param {BufferAttributeNode} node - The buffer attribute node.
     * @param {string} type - The node type.
     * @return {NodeAttribute} The node attribute.
     */
    getBufferAttributeFromNode( node, type ) {

        const nodeData = this.getDataFromNode( node );

        let bufferAttribute = nodeData.bufferAttribute;

        if ( bufferAttribute === undefined ) {

            const index = this.uniforms.index ++;

            bufferAttribute = new NodeAttribute( 'nodeAttribute' + index, type, node );

            this.bufferAttributes.push( bufferAttribute );

            nodeData.bufferAttribute = bufferAttribute;

        }

        return bufferAttribute;

    }

    /**
     * Returns an instance of {@link StructType} for the given output struct node.
     *
     * @param {OutputStructNode} node - The output struct node.
     * @param {Array<Object>} membersLayout - The output struct types.
     * @param {?string} [name=null] - The name of the struct.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @return {StructType} The struct type attribute.
     */
    getStructTypeFromNode( node, membersLayout, name = null, shaderStage = this.shaderStage ) {

        const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );

        let structType = nodeData.structType;

        if ( structType === undefined ) {

            const index = this.structs.index ++;

            if ( name === null ) name = 'StructType' + index;

            structType = new StructType( name, membersLayout );

            this.structs[ shaderStage ].push( structType );

            nodeData.structType = structType;

        }

        return structType;

    }

    /**
     * Returns an instance of {@link StructType} for the given output struct node.
     *
     * @param {OutputStructNode} node - The output struct node.
     * @param {Array<Object>} membersLayout - The output struct types.
     * @return {StructType} The struct type attribute.
     */
    getOutputStructTypeFromNode( node, membersLayout ) {

        const structType = this.getStructTypeFromNode( node, membersLayout, 'OutputType', 'fragment' );
        structType.output = true;

        return structType;

    }

    /**
     * Returns an instance of {@link NodeUniform} for the given uniform node.
     *
     * @param {UniformNode} node - The uniform node.
     * @param {string} type - The uniform type.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @param {?string} name - The name of the uniform.
     * @return {NodeUniform} The node uniform.
     */
    getUniformFromNode( node, type, shaderStage = this.shaderStage, name = null ) {

        const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );

        let nodeUniform = nodeData.uniform;

        if ( nodeUniform === undefined ) {

            const index = this.uniforms.index ++;

            nodeUniform = new NodeUniform( name || ( 'nodeUniform' + index ), type, node );

            this.uniforms[ shaderStage ].push( nodeUniform );

            this.registerDeclaration( nodeUniform );

            nodeData.uniform = nodeUniform;

        }

        return nodeUniform;

    }

    /**
     * Returns an instance of {@link NodeVar} for the given variable node.
     *
     * @param {VarNode} node - The variable node.
     * @param {?string} name - The variable's name.
     * @param {string} [type=node.getNodeType( this )] - The variable's type.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @param {boolean} [readOnly=false] - Whether the variable is read-only or not.
     *
     * @return {NodeVar} The node variable.
     */
    getVarFromNode( node, name = null, type = node.getNodeType( this ), shaderStage = this.shaderStage, readOnly = false ) {

        const nodeData = this.getDataFromNode( node, shaderStage );
        const subBuildVariable = this.getSubBuildProperty( 'variable', nodeData.subBuilds );

        let nodeVar = nodeData[ subBuildVariable ];

        if ( nodeVar === undefined ) {

            const idNS = readOnly ? '_const' : '_var';

            const vars = this.vars[ shaderStage ] || ( this.vars[ shaderStage ] = [] );
            const id = this.vars[ idNS ] || ( this.vars[ idNS ] = 0 );

            if ( name === null ) {

                name = ( readOnly ? 'nodeConst' : 'nodeVar' ) + id;

                this.vars[ idNS ] ++;

            }

            //

            if ( subBuildVariable !== 'variable' ) {

                name = this.getSubBuildProperty( name, nodeData.subBuilds );

            }

            //

            const count = node.getArrayCount( this );

            nodeVar = new NodeVar( name, type, readOnly, count );

            if ( ! readOnly ) {

                vars.push( nodeVar );

            }

            this.registerDeclaration( nodeVar );

            nodeData[ subBuildVariable ] = nodeVar;

        }

        return nodeVar;

    }

    /**
     * Returns whether a Node or its flow is deterministic, useful for use in `const`.
     *
     * @param {Node} node - The varying node.
     * @return {boolean} Returns true if deterministic.
     */
    isDeterministic( node ) {

        if ( node.isMathNode ) {

            return this.isDeterministic( node.aNode ) &&
                ( node.bNode ? this.isDeterministic( node.bNode ) : true ) &&
                ( node.cNode ? this.isDeterministic( node.cNode ) : true );

        } else if ( node.isOperatorNode ) {

            return this.isDeterministic( node.aNode ) &&
                ( node.bNode ? this.isDeterministic( node.bNode ) : true );

        } else if ( node.isArrayNode ) {

            if ( node.values !== null ) {

                for ( const n of node.values ) {

                    if ( ! this.isDeterministic( n ) ) {

                        return false;

                    }

                }

            }

            return true;

        } else if ( node.isConstNode ) {

            return true;

        }

        return false;

    }

    /**
     * Returns an instance of {@link NodeVarying} for the given varying node.
     *
     * @param {(VaryingNode|PropertyNode)} node - The varying node.
     * @param {?string} name - The varying's name.
     * @param {string} [type=node.getNodeType( this )] - The varying's type.
     * @param {?string} interpolationType - The interpolation type of the varying.
     * @param {?string} interpolationSampling - The interpolation sampling type of the varying.
     * @return {NodeVar} The node varying.
     */
    getVaryingFromNode( node, name = null, type = node.getNodeType( this ), interpolationType = null, interpolationSampling = null ) {

        const nodeData = this.getDataFromNode( node, 'any' );
        const subBuildVarying = this.getSubBuildProperty( 'varying', nodeData.subBuilds );

        let nodeVarying = nodeData[ subBuildVarying ];

        if ( nodeVarying === undefined ) {

            const varyings = this.varyings;
            const index = varyings.length;

            if ( name === null ) name = 'nodeVarying' + index;

            //

            if ( subBuildVarying !== 'varying' ) {

                name = this.getSubBuildProperty( name, nodeData.subBuilds );

            }

            //

            nodeVarying = new NodeVarying( name, type, interpolationType, interpolationSampling );

            varyings.push( nodeVarying );

            this.registerDeclaration( nodeVarying );

            nodeData[ subBuildVarying ] = nodeVarying;

        }

        return nodeVarying;

    }

    /**
     * Registers a node declaration in the current shader stage.
     *
     * @param {Object} node - The node to be registered.
     */
    registerDeclaration( node ) {

        const shaderStage = this.shaderStage;
        const declarations = this.declarations[ shaderStage ] || ( this.declarations[ shaderStage ] = {} );

        const property = this.getPropertyName( node );

        let index = 1;
        let name = property;

        // Automatically renames the property if the name is already in use.

        while ( declarations[ name ] !== undefined ) {

            name = property + '_' + index ++;

        }

        if ( index > 1 ) {

            node.name = name;

            console.warn( `THREE.TSL: Declaration name '${ property }' of '${ node.type }' already in use. Renamed to '${ name }'.` );

        }

        declarations[ name ] = node;

    }

    /**
     * Returns an instance of {@link NodeCode} for the given code node.
     *
     * @param {CodeNode} node - The code node.
     * @param {string} type - The node type.
     * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
     * @return {NodeCode} The node code.
     */
    getCodeFromNode( node, type, shaderStage = this.shaderStage ) {

        const nodeData = this.getDataFromNode( node );

        let nodeCode = nodeData.code;

        if ( nodeCode === undefined ) {

            const codes = this.codes[ shaderStage ] || ( this.codes[ shaderStage ] = [] );
            const index = codes.length;

            nodeCode = new NodeCode( 'nodeCode' + index, type );

            codes.push( nodeCode );

            nodeData.code = nodeCode;

        }

        return nodeCode;

    }

    /**
     * Adds a code flow based on the code-block hierarchy.

     * This is used so that code-blocks like If,Else create their variables locally if the Node
     * is only used inside one of these conditionals in the current shader stage.
     *
     * @param {Node} node - The node to add.
     * @param {Node} nodeBlock - Node-based code-block. Usually 'ConditionalNode'.
     */
    addFlowCodeHierarchy( node, nodeBlock ) {

        const { flowCodes, flowCodeBlock } = this.getDataFromNode( node );

        let needsFlowCode = true;
        let nodeBlockHierarchy = nodeBlock;

        while ( nodeBlockHierarchy ) {

            if ( flowCodeBlock.get( nodeBlockHierarchy ) === true ) {

                needsFlowCode = false;
                break;

            }

            nodeBlockHierarchy = this.getDataFromNode( nodeBlockHierarchy ).parentNodeBlock;

        }

        if ( needsFlowCode ) {

            for ( const flowCode of flowCodes ) {

                this.addLineFlowCode( flowCode );

            }

        }

    }

    /**
     * Add a inline-code to the current flow code-block.
     *
     * @param {Node} node - The node to add.
     * @param {string} code - The code to add.
     * @param {Node} nodeBlock - Current ConditionalNode
     */
    addLineFlowCodeBlock( node, code, nodeBlock ) {

        const nodeData = this.getDataFromNode( node );
        const flowCodes = nodeData.flowCodes || ( nodeData.flowCodes = [] );
        const codeBlock = nodeData.flowCodeBlock || ( nodeData.flowCodeBlock = new WeakMap() );

        flowCodes.push( code );
        codeBlock.set( nodeBlock, true );

    }

    /**
     * Add a inline-code to the current flow.
     *
     * @param {string} code - The code to add.
     * @param {?Node} [node= null] - Optional Node, can help the system understand if the Node is part of a code-block.
     * @return {NodeBuilder} A reference to this node builder.
     */
    addLineFlowCode( code, node = null ) {

        if ( code === '' ) return this;

        if ( node !== null && this.context.nodeBlock ) {

            this.addLineFlowCodeBlock( node, code, this.context.nodeBlock );

        }

        code = this.tab + code;

        if ( ! /;\s*$/.test( code ) ) {

            code = code + ';\n';

        }

        this.flow.code += code;

        return this;

    }

    /**
     * Adds a code to the current code flow.
     *
     * @param {string} code - Shader code.
     * @return {NodeBuilder} A reference to this node builder.
     */
    addFlowCode( code ) {

        this.flow.code += code;

        return this;

    }

    /**
     * Add tab in the code that will be generated so that other snippets respect the current tabulation.
     * Typically used in codes with If,Else.
     *
     * @return {NodeBuilder} A reference to this node builder.
     */
    addFlowTab() {

        this.tab += '\t';

        return this;

    }

    /**
     * Removes a tab.
     *
     * @return {NodeBuilder} A reference to this node builder.
     */
    removeFlowTab() {

        this.tab = this.tab.slice( 0, - 1 );

        return this;

    }

    /**
     * Gets the current flow data based on a Node.
     *
     * @param {Node} node - Node that the flow was started.
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {Object} The flow data.
     */
    getFlowData( node/*, shaderStage*/ ) {

        return this.flowsData.get( node );

    }

    /**
     * Executes the node flow based on a root node to generate the final shader code.
     *
     * @param {Node} node - The node to execute.
     * @return {Object} The code flow.
     */
    flowNode( node ) {

        const output = node.getNodeType( this );

        const flowData = this.flowChildNode( node, output );

        this.flowsData.set( node, flowData );

        return flowData;

    }

    /**
     * Includes a node in the current function node.
     *
     * @param {Node} node - The node to include.
     * @returns {void}
     */
    addInclude( node ) {

        if ( this.currentFunctionNode !== null ) {

            this.currentFunctionNode.includes.push( node );

        }

    }

    /**
     * Returns the native shader operator name for a given generic name.
     * It is a similar type of method like {@link NodeBuilder#getMethod}.
     *
     * @param {ShaderNodeInternal} shaderNode - The shader node to build the function node with.
     * @return {FunctionNode} The build function node.
     */
    buildFunctionNode( shaderNode ) {

        const fn = new FunctionNode();

        const previous = this.currentFunctionNode;

        this.currentFunctionNode = fn;

        fn.code = this.buildFunctionCode( shaderNode );

        this.currentFunctionNode = previous;

        return fn;

    }

    /**
     * Generates a code flow based on a TSL function: Fn().
     *
     * @param {ShaderNodeInternal} shaderNode - A function code will be generated based on the input.
     * @return {Object}
     */
    flowShaderNode( shaderNode ) {

        const layout = shaderNode.layout;

        const inputs = {
            [ Symbol.iterator ]() {

                let index = 0;
                const values = Object.values( this );
                return {
                    next: () => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )
                };

            }
        };

        for ( const input of layout.inputs ) {

            inputs[ input.name ] = new ParameterNode( input.type, input.name );

        }

        //

        shaderNode.layout = null;

        const callNode = shaderNode.call( inputs );
        const flowData = this.flowStagesNode( callNode, layout.type );

        shaderNode.layout = layout;

        return flowData;

    }

    /**
     * Executes the node in a specific build stage.
     *
     * @param {Node} node - The node to execute.
     * @param {string} buildStage - The build stage to execute the node in.
     * @param {Node|string|null} output - Expected output type. For example 'vec3'.
     * @return {Node|string|null} The result of the node build.
     */
    flowBuildStage( node, buildStage, output = null ) {

        const previousBuildStage = this.getBuildStage();

        this.setBuildStage( buildStage );

        const result = node.build( this, output );

        this.setBuildStage( previousBuildStage );

        return result;

    }

    /**
     * Runs the node flow through all the steps of creation, 'setup', 'analyze', 'generate'.
     *
     * @param {Node} node - The node to execute.
     * @param {?string} output - Expected output type. For example 'vec3'.
     * @return {Object}
     */
    flowStagesNode( node, output = null ) {

        const previousFlow = this.flow;
        const previousVars = this.vars;
        const previousDeclarations = this.declarations;
        const previousCache = this.cache;
        const previousBuildStage = this.buildStage;
        const previousStack = this.stack;

        const flow = {
            code: ''
        };

        this.flow = flow;
        this.vars = {};
        this.declarations = {};
        this.cache = new NodeCache();
        this.stack = stack();

        for ( const buildStage of defaultBuildStages ) {

            this.setBuildStage( buildStage );

            flow.result = node.build( this, output );

        }

        flow.vars = this.getVars( this.shaderStage );

        this.flow = previousFlow;
        this.vars = previousVars;
        this.declarations = previousDeclarations;
        this.cache = previousCache;
        this.stack = previousStack;

        this.setBuildStage( previousBuildStage );

        return flow;

    }

    /**
     * Returns the native shader operator name for a given generic name.
     * It is a similar type of method like {@link NodeBuilder#getMethod}.
     *
     * @abstract
     * @param {string} op - The operator name to resolve.
     * @return {?string} The resolved operator name.
     */
    getFunctionOperator( /* op */ ) {

        return null;

    }

    /**
     * Builds the given shader node.
     *
     * @abstract
     * @param {ShaderNodeInternal} shaderNode - The shader node.
     * @return {string} The function code.
     */
    buildFunctionCode( /* shaderNode */ ) {

        console.warn( 'Abstract function.' );

    }

    /**
     * Generates a code flow based on a child Node.
     *
     * @param {Node} node - The node to execute.
     * @param {?string} output - Expected output type. For example 'vec3'.
     * @return {Object} The code flow.
     */
    flowChildNode( node, output = null ) {

        const previousFlow = this.flow;

        const flow = {
            code: ''
        };

        this.flow = flow;

        flow.result = node.build( this, output );

        this.flow = previousFlow;

        return flow;

    }

    /**
     * Executes a flow of code in a different stage.
     *
     * Some nodes like `varying()` have the ability to compute code in vertex-stage and
     * return the value in fragment-stage even if it is being executed in an input fragment.
     *
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @param {Node} node - The node to execute.
     * @param {?string} output - Expected output type. For example 'vec3'.
     * @param {?string} propertyName - The property name to assign the result.
     * @return {Object|Node|null} The code flow or node.build() result.
     */
    flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) {

        const previousTab = this.tab;
        const previousCache = this.cache;
        const previousShaderStage = this.shaderStage;
        const previousContext = this.context;

        this.setShaderStage( shaderStage );

        const context = { ...this.context };
        delete context.nodeBlock;

        this.cache = this.globalCache;
        this.tab = '\t';
        this.context = context;

        let result = null;

        if ( this.buildStage === 'generate' ) {

            const flowData = this.flowChildNode( node, output );

            if ( propertyName !== null ) {

                flowData.code += `${ this.tab + propertyName } = ${ flowData.result };\n`;

            }

            this.flowCode[ shaderStage ] = this.flowCode[ shaderStage ] + flowData.code;

            result = flowData;

        } else {

            result = node.build( this );

        }

        this.setShaderStage( previousShaderStage );

        this.cache = previousCache;
        this.tab = previousTab;
        this.context = previousContext;

        return result;

    }

    /**
     * Returns an array holding all node attributes of this node builder.
     *
     * @return {Array<NodeAttribute>} The node attributes of this builder.
     */
    getAttributesArray() {

        return this.attributes.concat( this.bufferAttributes );

    }

    /**
     * Returns the attribute definitions as a shader string for the given shader stage.
     *
     * @abstract
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The attribute code section.
     */
    getAttributes( /*shaderStage*/ ) {

        console.warn( 'Abstract function.' );

    }

    /**
     * Returns the varying definitions as a shader string for the given shader stage.
     *
     * @abstract
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The varying code section.
     */
    getVaryings( /*shaderStage*/ ) {

        console.warn( 'Abstract function.' );

    }

    /**
     * Returns a single variable definition as a shader string for the given variable type and name.
     *
     * @param {string} type - The variable's type.
     * @param {string} name - The variable's name.
     * @param {?number} [count=null] - The array length.
     * @return {string} The shader string.
     */
    getVar( type, name, count = null ) {

        return `${ count !== null ? this.generateArrayDeclaration( type, count ) : this.getType( type ) } ${ name }`;

    }

    /**
     * Returns the variable definitions as a shader string for the given shader stage.
     *
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The variable code section.
     */
    getVars( shaderStage ) {

        let snippet = '';

        const vars = this.vars[ shaderStage ];

        if ( vars !== undefined ) {

            for ( const variable of vars ) {

                snippet += `${ this.getVar( variable.type, variable.name ) }; `;

            }

        }

        return snippet;

    }

    /**
     * Returns the uniform definitions as a shader string for the given shader stage.
     *
     * @abstract
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The uniform code section.
     */
    getUniforms( /*shaderStage*/ ) {

        console.warn( 'Abstract function.' );

    }

    /**
     * Returns the native code definitions as a shader string for the given shader stage.
     *
     * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
     * @return {string} The native code section.
     */
    getCodes( shaderStage ) {

        const codes = this.codes[ shaderStage ];

        let code = '';

        if ( codes !== undefined ) {

            for ( const nodeCode of codes ) {

                code += nodeCode.code + '\n';

            }

        }

        return code;

    }

    /**
     * Returns the hash of this node builder.
     *
     * @return {string} The hash.
     */
    getHash() {

        return this.vertexShader + this.fragmentShader + this.computeShader;

    }

    /**
     * Sets the current shader stage.
     *
     * @param {?('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage to set.
     */
    setShaderStage( shaderStage ) {

        this.shaderStage = shaderStage;

    }

    /**
     * Returns the current shader stage.
     *
     * @return {?('vertex'|'fragment'|'compute'|'any')} The current shader stage.
     */
    getShaderStage() {

        return this.shaderStage;

    }

    /**
     * Sets the current build stage.
     *
     * @param {?('setup'|'analyze'|'generate')} buildStage - The build stage to set.
     */
    setBuildStage( buildStage ) {

        this.buildStage = buildStage;

    }

    /**
     * Returns the current build stage.
     *
     * @return {?('setup'|'analyze'|'generate')} The current build stage.
     */
    getBuildStage() {

        return this.buildStage;

    }

    /**
     * Controls the code build of the shader stages.
     *
     * @abstract
     */
    buildCode() {

        console.warn( 'Abstract function.' );

    }

    /**
     * Returns the current sub-build layer.
     *
     * @return {SubBuildNode} The current sub-build layers.
     */
    get subBuild() {

        return this.subBuildLayers[ this.subBuildLayers.length - 1 ] || null;

    }

    /**
     * Adds a sub-build layer to the node builder.
     *
     * @param {SubBuildNode} subBuild - The sub-build layer to add.
     */
    addSubBuild( subBuild ) {

        this.subBuildLayers.push( subBuild );

    }

    /**
     * Removes the last sub-build layer from the node builder.
     *
     * @return {SubBuildNode} The removed sub-build layer.
     */
    removeSubBuild() {

        return this.subBuildLayers.pop();

    }

    /**
     * Returns the closest sub-build layer for the given data.
     *
     * @param {Node|Set|Array} data - The data to get the closest sub-build layer from.
     * @return {?string} The closest sub-build name or null if none found.
     */
    getClosestSubBuild( data ) {

        let subBuilds;

        if ( data && data.isNode ) {

            if ( data.isShaderCallNodeInternal ) {

                subBuilds = data.shaderNode.subBuilds;

            } else if ( data.isStackNode ) {

                subBuilds = [ data.subBuild ];

            } else {

                subBuilds = this.getDataFromNode( data, 'any' ).subBuilds;

            }

        } else if ( data instanceof Set ) {

            subBuilds = [ ...data ];

        } else {

            subBuilds = data;

        }

        if ( ! subBuilds ) return null;

        const subBuildLayers = this.subBuildLayers;

        for ( let i = subBuilds.length - 1; i >= 0; i -- ) {

            const subBuild = subBuilds[ i ];

            if ( subBuildLayers.includes( subBuild ) ) {

                return subBuild;

            }

        }

        return null;

    }


    /**
     * Returns the output node of a sub-build layer.
     *
     * @param {Node} node - The node to get the output from.
     * @return {string} The output node name.
     */
    getSubBuildOutput( node ) {

        return this.getSubBuildProperty( 'outputNode', node );

    }

    /**
     * Returns the sub-build property name for the given property and node.
     *
     * @param {string} [property=''] - The property name.
     * @param {?Node} [node=null] - The node to get the sub-build from.
     * @return {string} The sub-build property name.
     */
    getSubBuildProperty( property = '', node = null ) {

        let subBuild;

        if ( node !== null ) {

            subBuild = this.getClosestSubBuild( node );

        } else {

            subBuild = this.subBuildFn;

        }

        let result;

        if ( subBuild ) {

            result = property ? ( subBuild + '_' + property ) : subBuild;

        } else {

            result = property;

        }

        return result;

    }

    /**
     * Central build method which controls the build for the given object.
     *
     * @return {NodeBuilder} A reference to this node builder.
     */
    build() {

        const { object, material, renderer } = this;

        if ( material !== null ) {

            let nodeMaterial = renderer.library.fromMaterial( material );

            if ( nodeMaterial === null ) {

                console.error( `NodeMaterial: Material "${ material.type }" is not compatible.` );

                nodeMaterial = new NodeMaterial();

            }

            nodeMaterial.build( this );

        } else {

            this.addFlow( 'compute', object );

        }

        // setup() -> stage 1: create possible new nodes and/or return an output reference node
        // analyze()   -> stage 2: analyze nodes to possible optimization and validation
        // generate()  -> stage 3: generate shader

        for ( const buildStage of defaultBuildStages ) {

            this.setBuildStage( buildStage );

            if ( this.context.vertex && this.context.vertex.isNode ) {

                this.flowNodeFromShaderStage( 'vertex', this.context.vertex );

            }

            for ( const shaderStage of shaderStages ) {

                this.setShaderStage( shaderStage );

                const flowNodes = this.flowNodes[ shaderStage ];

                for ( const node of flowNodes ) {

                    if ( buildStage === 'generate' ) {

                        this.flowNode( node );

                    } else {

                        node.build( this );

                    }

                }

            }

        }

        this.setBuildStage( null );
        this.setShaderStage( null );

        // stage 4: build code for a specific output

        this.buildCode();
        this.buildUpdateNodes();

        return this;

    }

    /**
     * Returns a uniform representation which is later used for UBO generation and rendering.
     *
     * @param {NodeUniform} uniformNode - The uniform node.
     * @param {string} type - The requested type.
     * @return {Uniform} The uniform.
     */
    getNodeUniform( uniformNode, type ) {

        if ( type === 'float' || type === 'int' || type === 'uint' ) return new NumberNodeUniform( uniformNode );
        if ( type === 'vec2' || type === 'ivec2' || type === 'uvec2' ) return new Vector2NodeUniform( uniformNode );
        if ( type === 'vec3' || type === 'ivec3' || type === 'uvec3' ) return new Vector3NodeUniform( uniformNode );
        if ( type === 'vec4' || type === 'ivec4' || type === 'uvec4' ) return new Vector4NodeUniform( uniformNode );
        if ( type === 'color' ) return new ColorNodeUniform( uniformNode );
        if ( type === 'mat2' ) return new Matrix2NodeUniform( uniformNode );
        if ( type === 'mat3' ) return new Matrix3NodeUniform( uniformNode );
        if ( type === 'mat4' ) return new Matrix4NodeUniform( uniformNode );

        throw new Error( `Uniform "${type}" not declared.` );

    }

    /**
     * Formats the given shader snippet from a given type into another one. E.g.
     * this method might be used to convert a simple float string `"1.0"` into a
     * `vec3` representation: `"vec3<f32>( 1.0 )"`.
     *
     * @param {string} snippet - The shader snippet.
     * @param {string} fromType - The source type.
     * @param {string} toType - The target type.
     * @return {string} The updated shader string.
     */
    format( snippet, fromType, toType ) {

        fromType = this.getVectorType( fromType );
        toType = this.getVectorType( toType );

        if ( fromType === toType || toType === null || this.isReference( toType ) ) {

            return snippet;

        }

        const fromTypeLength = this.getTypeLength( fromType );
        const toTypeLength = this.getTypeLength( toType );

        if ( fromTypeLength === 16 && toTypeLength === 9 ) {

            return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xyz, ${ snippet }[ 1 ].xyz, ${ snippet }[ 2 ].xyz )`;

        }

        if ( fromTypeLength === 9 && toTypeLength === 4 ) {

            return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xy, ${ snippet }[ 1 ].xy )`;

        }


        if ( fromTypeLength > 4 ) { // fromType is matrix-like

            // @TODO: ignore for now

            return snippet;

        }

        if ( toTypeLength > 4 || toTypeLength === 0 ) { // toType is matrix-like or unknown

            // @TODO: ignore for now

            return snippet;

        }

        if ( fromTypeLength === toTypeLength ) {

            return `${ this.getType( toType ) }( ${ snippet } )`;

        }

        if ( fromTypeLength > toTypeLength ) {

            snippet = toType === 'bool' ? `all( ${ snippet } )` : `${ snippet }.${ 'xyz'.slice( 0, toTypeLength ) }`;

            return this.format( snippet, this.getTypeFromLength( toTypeLength, this.getComponentType( fromType ) ), toType );

        }

        if ( toTypeLength === 4 && fromTypeLength > 1 ) { // toType is vec4-like

            return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec3' ) }, 1.0 )`;

        }

        if ( fromTypeLength === 2 ) { // fromType is vec2-like and toType is vec3-like

            return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec2' ) }, 0.0 )`;

        }

        if ( fromTypeLength === 1 && toTypeLength > 1 && fromType !== this.getComponentType( toType ) ) { // fromType is float-like

            // convert a number value to vector type, e.g:
            // vec3( 1u ) -> vec3( float( 1u ) )

            snippet = `${ this.getType( this.getComponentType( toType ) ) }( ${ snippet } )`;

        }

        return `${ this.getType( toType ) }( ${ snippet } )`; // fromType is float-like

    }

    /**
     * Returns a signature with the engine's current revision.
     *
     * @return {string} The signature.
     */
    getSignature() {

        return `// Three.js r${ REVISION } - Node System\n`;

    }

}

Methods

getBindGroupsCache(): ChainMap
Code
getBindGroupsCache() {

        let bindGroupsCache = rendererCache.get( this.renderer );

        if ( bindGroupsCache === undefined ) {

            bindGroupsCache = new ChainMap();

            rendererCache.set( this.renderer, bindGroupsCache );

        }

        return bindGroupsCache;

    }
createRenderTarget(width: number, height: number, options: any): RenderTarget
Code
createRenderTarget( width, height, options ) {

        return new RenderTarget( width, height, options );

    }
createCubeRenderTarget(size: number, options: any): CubeRenderTarget
Code
createCubeRenderTarget( size, options ) {

        return new CubeRenderTarget( size, options );

    }
includes(node: Node): boolean
Code
includes( node ) {

        return this.nodes.includes( node );

    }
getOutputStructName(): string
Code
getOutputStructName() {}
_getBindGroup(groupName: string, bindings: NodeUniformsGroup[]): BindGroup
Code
_getBindGroup( groupName, bindings ) {

        const bindGroupsCache = this.getBindGroupsCache();

        //

        const bindingsArray = [];

        let sharedGroup = true;

        for ( const binding of bindings ) {

            bindingsArray.push( binding );

            sharedGroup = sharedGroup && binding.groupNode.shared !== true;

        }

        //

        let bindGroup;

        if ( sharedGroup ) {

            bindGroup = bindGroupsCache.get( bindingsArray );

            if ( bindGroup === undefined ) {

                bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );

                bindGroupsCache.set( bindingsArray, bindGroup );

            }

        } else {

            bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );

        }

        return bindGroup;

    }
getBindGroupArray(groupName: string, shaderStage: "compute" | "vertex" | "fragment" | "any"): NodeUniformsGroup[]
Code
getBindGroupArray( groupName, shaderStage ) {

        const bindings = this.bindings[ shaderStage ];

        let bindGroup = bindings[ groupName ];

        if ( bindGroup === undefined ) {

            if ( this.bindingsIndexes[ groupName ] === undefined ) {

                this.bindingsIndexes[ groupName ] = { binding: 0, group: Object.keys( this.bindingsIndexes ).length };

            }

            bindings[ groupName ] = bindGroup = [];

        }

        return bindGroup;

    }
getBindings(): BindGroup[]
Code
getBindings() {

        let bindingsGroups = this.bindGroups;

        if ( bindingsGroups === null ) {

            const groups = {};
            const bindings = this.bindings;

            for ( const shaderStage of shaderStages ) {

                for ( const groupName in bindings[ shaderStage ] ) {

                    const uniforms = bindings[ shaderStage ][ groupName ];

                    const groupUniforms = groups[ groupName ] || ( groups[ groupName ] = [] );
                    groupUniforms.push( ...uniforms );

                }

            }

            bindingsGroups = [];

            for ( const groupName in groups ) {

                const group = groups[ groupName ];

                const bindingsGroup = this._getBindGroup( groupName, group );

                bindingsGroups.push( bindingsGroup );

            }

            this.bindGroups = bindingsGroups;

        }

        return bindingsGroups;

    }
sortBindingGroups(): void
Code
sortBindingGroups() {

        const bindingsGroups = this.getBindings();

        bindingsGroups.sort( ( a, b ) => ( a.bindings[ 0 ].groupNode.order - b.bindings[ 0 ].groupNode.order ) );

        for ( let i = 0; i < bindingsGroups.length; i ++ ) {

            const bindingGroup = bindingsGroups[ i ];
            this.bindingsIndexes[ bindingGroup.name ].group = i;

            bindingGroup.index = i;

        }

    }
setHashNode(node: Node, hash: number): void
Code
setHashNode( node, hash ) {

        this.hashNodes[ hash ] = node;

    }
addNode(node: Node): void
Code
addNode( node ) {

        if ( this.nodes.includes( node ) === false ) {

            this.nodes.push( node );

            this.setHashNode( node, node.getHash( this ) );

        }

    }
addSequentialNode(node: Node): void
Code
addSequentialNode( node ) {

        if ( this.sequentialNodes.includes( node ) === false ) {

            this.sequentialNodes.push( node );

        }

    }
buildUpdateNodes(): void
Code
buildUpdateNodes() {

        for ( const node of this.nodes ) {

            const updateType = node.getUpdateType();

            if ( updateType !== NodeUpdateType.NONE ) {

                this.updateNodes.push( node.getSelf() );

            }

        }

        for ( const node of this.sequentialNodes ) {

            const updateBeforeType = node.getUpdateBeforeType();
            const updateAfterType = node.getUpdateAfterType();

            if ( updateBeforeType !== NodeUpdateType.NONE ) {

                this.updateBeforeNodes.push( node.getSelf() );

            }

            if ( updateAfterType !== NodeUpdateType.NONE ) {

                this.updateAfterNodes.push( node.getSelf() );

            }

        }

    }
isFilteredTexture(texture: Texture): boolean
Code
isFilteredTexture( texture ) {

        return ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter ||
            texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter );

    }
addChain(node: Node): void
Code
addChain( node ) {

        /*
        if ( this.chaining.indexOf( node ) !== - 1 ) {

            console.warn( 'Recursive node: ', node );

        }
        */

        this.chaining.push( node );

    }
removeChain(node: Node): void
Code
removeChain( node ) {

        const lastChain = this.chaining.pop();

        if ( lastChain !== node ) {

            throw new Error( 'NodeBuilder: Invalid node chaining!' );

        }

    }
getMethod(method: string): string
Code
getMethod( method ) {

        return method;

    }
getTernary(): string
Code
getTernary( /* condSnippet, ifSnippet, elseSnippet*/ ) {

        return null;

    }
getNodeFromHash(hash: number): Node
Code
getNodeFromHash( hash ) {

        return this.hashNodes[ hash ];

    }
addFlow(shaderStage: "compute" | "vertex" | "fragment", node: Node): Node
Code
addFlow( shaderStage, node ) {

        this.flowNodes[ shaderStage ].push( node );

        return node;

    }
setContext(context: any): void
Code
setContext( context ) {

        this.context = context;

    }
getContext(): any
Code
getContext() {

        return this.context;

    }
getSharedContext(): any
Code
getSharedContext() {

        const context = { ...this.context };

        delete context.material;

        return this.context;

    }
setCache(cache: NodeCache): void
Code
setCache( cache ) {

        this.cache = cache;

    }
getCache(): NodeCache
Code
getCache() {

        return this.cache;

    }
getCacheFromNode(node: Node, parent: boolean): NodeCache
Code
getCacheFromNode( node, parent = true ) {

        const data = this.getDataFromNode( node );
        if ( data.cache === undefined ) data.cache = new NodeCache( parent ? this.getCache() : null );

        return data.cache;

    }
isAvailable(): boolean
Code
isAvailable( /*name*/ ) {

        return false;

    }
getVertexIndex(): string
Code
getVertexIndex() {

        console.warn( 'Abstract function.' );

    }
getInstanceIndex(): string
Code
getInstanceIndex() {

        console.warn( 'Abstract function.' );

    }
getDrawIndex(): string
Code
getDrawIndex() {

        console.warn( 'Abstract function.' );

    }
getFrontFacing(): string
Code
getFrontFacing() {

        console.warn( 'Abstract function.' );

    }
getFragCoord(): string
Code
getFragCoord() {

        console.warn( 'Abstract function.' );

    }
isFlipY(): boolean
Code
isFlipY() {

        return false;

    }
increaseUsage(node: Node): number
Code
increaseUsage( node ) {

        const nodeData = this.getDataFromNode( node );
        nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1;

        return nodeData.usageCount;

    }
generateTexture(): string
Code
generateTexture( /* texture, textureProperty, uvSnippet */ ) {

        console.warn( 'Abstract function.' );

    }
generateTextureLod(): string
Code
generateTextureLod( /* texture, textureProperty, uvSnippet, depthSnippet, levelSnippet */ ) {

        console.warn( 'Abstract function.' );

    }
generateArrayDeclaration(type: string, count: number): string
Code
generateArrayDeclaration( type, count ) {

        return this.getType( type ) + '[ ' + count + ' ]';

    }
generateArray(type: string, count: number, values: Node[]): string
Code
generateArray( type, count, values = null ) {

        let snippet = this.generateArrayDeclaration( type, count ) + '( ';

        for ( let i = 0; i < count; i ++ ) {

            const value = values ? values[ i ] : null;

            if ( value !== null ) {

                snippet += value.build( this, type );

            } else {

                snippet += this.generateConst( type );

            }

            if ( i < count - 1 ) snippet += ', ';

        }

        snippet += ' )';

        return snippet;

    }
generateStruct(type: string, membersLayout: any[], values: Node[]): string
Code
generateStruct( type, membersLayout, values = null ) {

        const snippets = [];

        for ( const member of membersLayout ) {

            const { name, type } = member;

            if ( values && values[ name ] && values[ name ].isNode ) {

                snippets.push( values[ name ].build( this, type ) );

            } else {

                snippets.push( this.generateConst( type ) );

            }

        }

        return type + '( ' + snippets.join( ', ' ) + ' )';

    }
generateConst(type: string, value: any): string
Code
generateConst( type, value = null ) {

        if ( value === null ) {

            if ( type === 'float' || type === 'int' || type === 'uint' ) value = 0;
            else if ( type === 'bool' ) value = false;
            else if ( type === 'color' ) value = new Color();
            else if ( type === 'vec2' ) value = new Vector2();
            else if ( type === 'vec3' ) value = new Vector3();
            else if ( type === 'vec4' ) value = new Vector4();

        }

        if ( type === 'float' ) return toFloat( value );
        if ( type === 'int' ) return `${ Math.round( value ) }`;
        if ( type === 'uint' ) return value >= 0 ? `${ Math.round( value ) }u` : '0u';
        if ( type === 'bool' ) return value ? 'true' : 'false';
        if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ toFloat( value.r ) }, ${ toFloat( value.g ) }, ${ toFloat( value.b ) } )`;

        const typeLength = this.getTypeLength( type );

        const componentType = this.getComponentType( type );

        const generateConst = value => this.generateConst( componentType, value );

        if ( typeLength === 2 ) {

            return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) } )`;

        } else if ( typeLength === 3 ) {

            return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) } )`;

        } else if ( typeLength === 4 && type !== 'mat2' ) {

            return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) }, ${ generateConst( value.w ) } )`;

        } else if ( typeLength >= 4 && value && ( value.isMatrix2 || value.isMatrix3 || value.isMatrix4 ) ) {

            return `${ this.getType( type ) }( ${ value.elements.map( generateConst ).join( ', ' ) } )`;

        } else if ( typeLength > 4 ) {

            return `${ this.getType( type ) }()`;

        }

        throw new Error( `NodeBuilder: Type '${type}' not found in generate constant attempt.` );

    }
getType(type: string): string
Code
getType( type ) {

        if ( type === 'color' ) return 'vec3';

        return type;

    }
hasGeometryAttribute(name: string): boolean
Code
hasGeometryAttribute( name ) {

        return this.geometry && this.geometry.getAttribute( name ) !== undefined;

    }
getAttribute(name: string, type: string): NodeAttribute
Code
getAttribute( name, type ) {

        const attributes = this.attributes;

        // find attribute

        for ( const attribute of attributes ) {

            if ( attribute.name === name ) {

                return attribute;

            }

        }

        // create a new if no exist

        const attribute = new NodeAttribute( name, type );

        this.registerDeclaration( attribute );

        attributes.push( attribute );

        return attribute;

    }
getPropertyName(node: Node): string
Code
getPropertyName( node/*, shaderStage*/ ) {

        return node.name;

    }
isVector(type: string): boolean
Code
isVector( type ) {

        return /vec\d/.test( type );

    }
isMatrix(type: string): boolean
Code
isMatrix( type ) {

        return /mat\d/.test( type );

    }
isReference(type: string): boolean
Code
isReference( type ) {

        return type === 'void' || type === 'property' || type === 'sampler' || type === 'samplerComparison' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'depthTexture' || type === 'texture3D';

    }
needsToWorkingColorSpace(): boolean
Code
needsToWorkingColorSpace( /*texture*/ ) {

        return false;

    }
getComponentTypeFromTexture(texture: Texture): string
Code
getComponentTypeFromTexture( texture ) {

        const type = texture.type;

        if ( texture.isDataTexture ) {

            if ( type === IntType ) return 'int';
            if ( type === UnsignedIntType ) return 'uint';

        }

        return 'float';

    }
getElementType(type: string): string
Code
getElementType( type ) {

        if ( type === 'mat2' ) return 'vec2';
        if ( type === 'mat3' ) return 'vec3';
        if ( type === 'mat4' ) return 'vec4';

        return this.getComponentType( type );

    }
getComponentType(type: string): string
Code
getComponentType( type ) {

        type = this.getVectorType( type );

        if ( type === 'float' || type === 'bool' || type === 'int' || type === 'uint' ) return type;

        const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec( type );

        if ( componentType === null ) return null;

        if ( componentType[ 1 ] === 'b' ) return 'bool';
        if ( componentType[ 1 ] === 'i' ) return 'int';
        if ( componentType[ 1 ] === 'u' ) return 'uint';

        return 'float';

    }
getVectorType(type: string): string
Code
getVectorType( type ) {

        if ( type === 'color' ) return 'vec3';
        if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) return 'vec4';

        return type;

    }
getTypeFromLength(length: number, componentType: string): string
Code
getTypeFromLength( length, componentType = 'float' ) {

        if ( length === 1 ) return componentType;

        let baseType = getTypeFromLength( length );
        const prefix = componentType === 'float' ? '' : componentType[ 0 ];

        // fix edge case for mat2x2 being same size as vec4
        if ( /mat2/.test( componentType ) === true ) {

            baseType = baseType.replace( 'vec', 'mat' );

        }

        return prefix + baseType;

    }
getTypeFromArray(array: TypedArray): string
Code
getTypeFromArray( array ) {

        return typeFromArray.get( array.constructor );

    }
isInteger(type: string): boolean
Code
isInteger( type ) {

        return /int|uint|(i|u)vec/.test( type );

    }
getTypeFromAttribute(attribute: BufferAttribute): string
Code
getTypeFromAttribute( attribute ) {

        let dataAttribute = attribute;

        if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data;

        const array = dataAttribute.array;
        const itemSize = attribute.itemSize;
        const normalized = attribute.normalized;

        let arrayType;

        if ( ! ( attribute instanceof Float16BufferAttribute ) && normalized !== true ) {

            arrayType = this.getTypeFromArray( array );

        }

        return this.getTypeFromLength( itemSize, arrayType );

    }
getTypeLength(type: string): number
Code
getTypeLength( type ) {

        const vecType = this.getVectorType( type );
        const vecNum = /vec([2-4])/.exec( vecType );

        if ( vecNum !== null ) return Number( vecNum[ 1 ] );
        if ( vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint' ) return 1;
        if ( /mat2/.test( type ) === true ) return 4;
        if ( /mat3/.test( type ) === true ) return 9;
        if ( /mat4/.test( type ) === true ) return 16;

        return 0;

    }
getVectorFromMatrix(type: string): string
Code
getVectorFromMatrix( type ) {

        return type.replace( 'mat', 'vec' );

    }
changeComponentType(type: string, newComponentType: string): string
Code
changeComponentType( type, newComponentType ) {

        return this.getTypeFromLength( this.getTypeLength( type ), newComponentType );

    }
getIntegerType(type: string): string
Code
getIntegerType( type ) {

        const componentType = this.getComponentType( type );

        if ( componentType === 'int' || componentType === 'uint' ) return type;

        return this.changeComponentType( type, 'int' );

    }
addStack(): StackNode
Code
addStack() {

        this.stack = stack( this.stack );

        this.stacks.push( getCurrentStack() || this.stack );
        setCurrentStack( this.stack );

        return this.stack;

    }
removeStack(): StackNode
Code
removeStack() {

        const lastStack = this.stack;
        this.stack = lastStack.parent;

        setCurrentStack( this.stacks.pop() );

        return lastStack;

    }
getDataFromNode(node: Node, shaderStage: "compute" | "vertex" | "fragment" | "any", cache: NodeCache): any
Code
getDataFromNode( node, shaderStage = this.shaderStage, cache = null ) {

        cache = cache === null ? ( node.isGlobal( this ) ? this.globalCache : this.cache ) : cache;

        let nodeData = cache.getData( node );

        if ( nodeData === undefined ) {

            nodeData = {};

            cache.setData( node, nodeData );

        }

        if ( nodeData[ shaderStage ] === undefined ) nodeData[ shaderStage ] = {};

        //

        let data = nodeData[ shaderStage ];

        const subBuilds = nodeData.any ? nodeData.any.subBuilds : null;
        const subBuild = this.getClosestSubBuild( subBuilds );

        if ( subBuild ) {

            if ( data.subBuildsCache === undefined ) data.subBuildsCache = {};

            data = data.subBuildsCache[ subBuild ] || ( data.subBuildsCache[ subBuild ] = {} );
            data.subBuilds = subBuilds;

        }

        return data;

    }
getNodeProperties(node: Node, shaderStage: "compute" | "vertex" | "fragment" | "any"): any
Code
getNodeProperties( node, shaderStage = 'any' ) {

        const nodeData = this.getDataFromNode( node, shaderStage );

        return nodeData.properties || ( nodeData.properties = { outputNode: null } );

    }
getBufferAttributeFromNode(node: BufferAttributeNode, type: string): NodeAttribute
Code
getBufferAttributeFromNode( node, type ) {

        const nodeData = this.getDataFromNode( node );

        let bufferAttribute = nodeData.bufferAttribute;

        if ( bufferAttribute === undefined ) {

            const index = this.uniforms.index ++;

            bufferAttribute = new NodeAttribute( 'nodeAttribute' + index, type, node );

            this.bufferAttributes.push( bufferAttribute );

            nodeData.bufferAttribute = bufferAttribute;

        }

        return bufferAttribute;

    }
getStructTypeFromNode(node: OutputStructNode, membersLayout: any[], name: string, shaderStage: "compute" | "vertex" | "fragment" | "any"): StructType
Code
getStructTypeFromNode( node, membersLayout, name = null, shaderStage = this.shaderStage ) {

        const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );

        let structType = nodeData.structType;

        if ( structType === undefined ) {

            const index = this.structs.index ++;

            if ( name === null ) name = 'StructType' + index;

            structType = new StructType( name, membersLayout );

            this.structs[ shaderStage ].push( structType );

            nodeData.structType = structType;

        }

        return structType;

    }
getOutputStructTypeFromNode(node: OutputStructNode, membersLayout: any[]): StructType
Code
getOutputStructTypeFromNode( node, membersLayout ) {

        const structType = this.getStructTypeFromNode( node, membersLayout, 'OutputType', 'fragment' );
        structType.output = true;

        return structType;

    }
getUniformFromNode(node: UniformNode, type: string, shaderStage: "compute" | "vertex" | "fragment" | "any", name: string): NodeUniform
Code
getUniformFromNode( node, type, shaderStage = this.shaderStage, name = null ) {

        const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );

        let nodeUniform = nodeData.uniform;

        if ( nodeUniform === undefined ) {

            const index = this.uniforms.index ++;

            nodeUniform = new NodeUniform( name || ( 'nodeUniform' + index ), type, node );

            this.uniforms[ shaderStage ].push( nodeUniform );

            this.registerDeclaration( nodeUniform );

            nodeData.uniform = nodeUniform;

        }

        return nodeUniform;

    }
getVarFromNode(node: VarNode, name: string, type: string, shaderStage: "compute" | "vertex" | "fragment" | "any", readOnly: boolean): NodeVar
Code
getVarFromNode( node, name = null, type = node.getNodeType( this ), shaderStage = this.shaderStage, readOnly = false ) {

        const nodeData = this.getDataFromNode( node, shaderStage );
        const subBuildVariable = this.getSubBuildProperty( 'variable', nodeData.subBuilds );

        let nodeVar = nodeData[ subBuildVariable ];

        if ( nodeVar === undefined ) {

            const idNS = readOnly ? '_const' : '_var';

            const vars = this.vars[ shaderStage ] || ( this.vars[ shaderStage ] = [] );
            const id = this.vars[ idNS ] || ( this.vars[ idNS ] = 0 );

            if ( name === null ) {

                name = ( readOnly ? 'nodeConst' : 'nodeVar' ) + id;

                this.vars[ idNS ] ++;

            }

            //

            if ( subBuildVariable !== 'variable' ) {

                name = this.getSubBuildProperty( name, nodeData.subBuilds );

            }

            //

            const count = node.getArrayCount( this );

            nodeVar = new NodeVar( name, type, readOnly, count );

            if ( ! readOnly ) {

                vars.push( nodeVar );

            }

            this.registerDeclaration( nodeVar );

            nodeData[ subBuildVariable ] = nodeVar;

        }

        return nodeVar;

    }
isDeterministic(node: Node): boolean
Code
isDeterministic( node ) {

        if ( node.isMathNode ) {

            return this.isDeterministic( node.aNode ) &&
                ( node.bNode ? this.isDeterministic( node.bNode ) : true ) &&
                ( node.cNode ? this.isDeterministic( node.cNode ) : true );

        } else if ( node.isOperatorNode ) {

            return this.isDeterministic( node.aNode ) &&
                ( node.bNode ? this.isDeterministic( node.bNode ) : true );

        } else if ( node.isArrayNode ) {

            if ( node.values !== null ) {

                for ( const n of node.values ) {

                    if ( ! this.isDeterministic( n ) ) {

                        return false;

                    }

                }

            }

            return true;

        } else if ( node.isConstNode ) {

            return true;

        }

        return false;

    }
getVaryingFromNode(node: any, name: string, type: string, interpolationType: string, interpolationSampling: string): NodeVar
Code
getVaryingFromNode( node, name = null, type = node.getNodeType( this ), interpolationType = null, interpolationSampling = null ) {

        const nodeData = this.getDataFromNode( node, 'any' );
        const subBuildVarying = this.getSubBuildProperty( 'varying', nodeData.subBuilds );

        let nodeVarying = nodeData[ subBuildVarying ];

        if ( nodeVarying === undefined ) {

            const varyings = this.varyings;
            const index = varyings.length;

            if ( name === null ) name = 'nodeVarying' + index;

            //

            if ( subBuildVarying !== 'varying' ) {

                name = this.getSubBuildProperty( name, nodeData.subBuilds );

            }

            //

            nodeVarying = new NodeVarying( name, type, interpolationType, interpolationSampling );

            varyings.push( nodeVarying );

            this.registerDeclaration( nodeVarying );

            nodeData[ subBuildVarying ] = nodeVarying;

        }

        return nodeVarying;

    }
registerDeclaration(node: any): void
Code
registerDeclaration( node ) {

        const shaderStage = this.shaderStage;
        const declarations = this.declarations[ shaderStage ] || ( this.declarations[ shaderStage ] = {} );

        const property = this.getPropertyName( node );

        let index = 1;
        let name = property;

        // Automatically renames the property if the name is already in use.

        while ( declarations[ name ] !== undefined ) {

            name = property + '_' + index ++;

        }

        if ( index > 1 ) {

            node.name = name;

            console.warn( `THREE.TSL: Declaration name '${ property }' of '${ node.type }' already in use. Renamed to '${ name }'.` );

        }

        declarations[ name ] = node;

    }
getCodeFromNode(node: CodeNode, type: string, shaderStage: "compute" | "vertex" | "fragment" | "any"): NodeCode
Code
getCodeFromNode( node, type, shaderStage = this.shaderStage ) {

        const nodeData = this.getDataFromNode( node );

        let nodeCode = nodeData.code;

        if ( nodeCode === undefined ) {

            const codes = this.codes[ shaderStage ] || ( this.codes[ shaderStage ] = [] );
            const index = codes.length;

            nodeCode = new NodeCode( 'nodeCode' + index, type );

            codes.push( nodeCode );

            nodeData.code = nodeCode;

        }

        return nodeCode;

    }
addFlowCodeHierarchy(node: Node, nodeBlock: Node): void
Code
addFlowCodeHierarchy( node, nodeBlock ) {

        const { flowCodes, flowCodeBlock } = this.getDataFromNode( node );

        let needsFlowCode = true;
        let nodeBlockHierarchy = nodeBlock;

        while ( nodeBlockHierarchy ) {

            if ( flowCodeBlock.get( nodeBlockHierarchy ) === true ) {

                needsFlowCode = false;
                break;

            }

            nodeBlockHierarchy = this.getDataFromNode( nodeBlockHierarchy ).parentNodeBlock;

        }

        if ( needsFlowCode ) {

            for ( const flowCode of flowCodes ) {

                this.addLineFlowCode( flowCode );

            }

        }

    }
addLineFlowCodeBlock(node: Node, code: string, nodeBlock: Node): void
Code
addLineFlowCodeBlock( node, code, nodeBlock ) {

        const nodeData = this.getDataFromNode( node );
        const flowCodes = nodeData.flowCodes || ( nodeData.flowCodes = [] );
        const codeBlock = nodeData.flowCodeBlock || ( nodeData.flowCodeBlock = new WeakMap() );

        flowCodes.push( code );
        codeBlock.set( nodeBlock, true );

    }
addLineFlowCode(code: string, node: Node): NodeBuilder
Code
addLineFlowCode( code, node = null ) {

        if ( code === '' ) return this;

        if ( node !== null && this.context.nodeBlock ) {

            this.addLineFlowCodeBlock( node, code, this.context.nodeBlock );

        }

        code = this.tab + code;

        if ( ! /;\s*$/.test( code ) ) {

            code = code + ';\n';

        }

        this.flow.code += code;

        return this;

    }
addFlowCode(code: string): NodeBuilder
Code
addFlowCode( code ) {

        this.flow.code += code;

        return this;

    }
addFlowTab(): NodeBuilder
Code
addFlowTab() {

        this.tab += '\t';

        return this;

    }
removeFlowTab(): NodeBuilder
Code
removeFlowTab() {

        this.tab = this.tab.slice( 0, - 1 );

        return this;

    }
getFlowData(node: Node): any
Code
getFlowData( node/*, shaderStage*/ ) {

        return this.flowsData.get( node );

    }
flowNode(node: Node): any
Code
flowNode( node ) {

        const output = node.getNodeType( this );

        const flowData = this.flowChildNode( node, output );

        this.flowsData.set( node, flowData );

        return flowData;

    }
addInclude(node: Node): void
Code
addInclude( node ) {

        if ( this.currentFunctionNode !== null ) {

            this.currentFunctionNode.includes.push( node );

        }

    }
buildFunctionNode(shaderNode: ShaderNodeInternal): FunctionNode
Code
buildFunctionNode( shaderNode ) {

        const fn = new FunctionNode();

        const previous = this.currentFunctionNode;

        this.currentFunctionNode = fn;

        fn.code = this.buildFunctionCode( shaderNode );

        this.currentFunctionNode = previous;

        return fn;

    }
flowShaderNode(shaderNode: ShaderNodeInternal): any
Code
flowShaderNode( shaderNode ) {

        const layout = shaderNode.layout;

        const inputs = {
            [ Symbol.iterator ]() {

                let index = 0;
                const values = Object.values( this );
                return {
                    next: () => ( {
                        value: values[ index ],
                        done: index ++ >= values.length
                    } )
                };

            }
        };

        for ( const input of layout.inputs ) {

            inputs[ input.name ] = new ParameterNode( input.type, input.name );

        }

        //

        shaderNode.layout = null;

        const callNode = shaderNode.call( inputs );
        const flowData = this.flowStagesNode( callNode, layout.type );

        shaderNode.layout = layout;

        return flowData;

    }
flowBuildStage(node: Node, buildStage: string, output: string | Node): string | Node
Code
flowBuildStage( node, buildStage, output = null ) {

        const previousBuildStage = this.getBuildStage();

        this.setBuildStage( buildStage );

        const result = node.build( this, output );

        this.setBuildStage( previousBuildStage );

        return result;

    }
flowStagesNode(node: Node, output: string): any
Code
flowStagesNode( node, output = null ) {

        const previousFlow = this.flow;
        const previousVars = this.vars;
        const previousDeclarations = this.declarations;
        const previousCache = this.cache;
        const previousBuildStage = this.buildStage;
        const previousStack = this.stack;

        const flow = {
            code: ''
        };

        this.flow = flow;
        this.vars = {};
        this.declarations = {};
        this.cache = new NodeCache();
        this.stack = stack();

        for ( const buildStage of defaultBuildStages ) {

            this.setBuildStage( buildStage );

            flow.result = node.build( this, output );

        }

        flow.vars = this.getVars( this.shaderStage );

        this.flow = previousFlow;
        this.vars = previousVars;
        this.declarations = previousDeclarations;
        this.cache = previousCache;
        this.stack = previousStack;

        this.setBuildStage( previousBuildStage );

        return flow;

    }
getFunctionOperator(): string
Code
getFunctionOperator( /* op */ ) {

        return null;

    }
buildFunctionCode(): string
Code
buildFunctionCode( /* shaderNode */ ) {

        console.warn( 'Abstract function.' );

    }
flowChildNode(node: Node, output: string): any
Code
flowChildNode( node, output = null ) {

        const previousFlow = this.flow;

        const flow = {
            code: ''
        };

        this.flow = flow;

        flow.result = node.build( this, output );

        this.flow = previousFlow;

        return flow;

    }
flowNodeFromShaderStage(shaderStage: "compute" | "vertex" | "fragment" | "any", node: Node, output: string, propertyName: string): any
Code
flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) {

        const previousTab = this.tab;
        const previousCache = this.cache;
        const previousShaderStage = this.shaderStage;
        const previousContext = this.context;

        this.setShaderStage( shaderStage );

        const context = { ...this.context };
        delete context.nodeBlock;

        this.cache = this.globalCache;
        this.tab = '\t';
        this.context = context;

        let result = null;

        if ( this.buildStage === 'generate' ) {

            const flowData = this.flowChildNode( node, output );

            if ( propertyName !== null ) {

                flowData.code += `${ this.tab + propertyName } = ${ flowData.result };\n`;

            }

            this.flowCode[ shaderStage ] = this.flowCode[ shaderStage ] + flowData.code;

            result = flowData;

        } else {

            result = node.build( this );

        }

        this.setShaderStage( previousShaderStage );

        this.cache = previousCache;
        this.tab = previousTab;
        this.context = previousContext;

        return result;

    }
getAttributesArray(): NodeAttribute[]
Code
getAttributesArray() {

        return this.attributes.concat( this.bufferAttributes );

    }
getAttributes(): string
Code
getAttributes( /*shaderStage*/ ) {

        console.warn( 'Abstract function.' );

    }
getVaryings(): string
Code
getVaryings( /*shaderStage*/ ) {

        console.warn( 'Abstract function.' );

    }
getVar(type: string, name: string, count: number): string
Code
getVar( type, name, count = null ) {

        return `${ count !== null ? this.generateArrayDeclaration( type, count ) : this.getType( type ) } ${ name }`;

    }
getVars(shaderStage: "compute" | "vertex" | "fragment" | "any"): string
Code
getVars( shaderStage ) {

        let snippet = '';

        const vars = this.vars[ shaderStage ];

        if ( vars !== undefined ) {

            for ( const variable of vars ) {

                snippet += `${ this.getVar( variable.type, variable.name ) }; `;

            }

        }

        return snippet;

    }
getUniforms(): string
Code
getUniforms( /*shaderStage*/ ) {

        console.warn( 'Abstract function.' );

    }
getCodes(shaderStage: "compute" | "vertex" | "fragment" | "any"): string
Code
getCodes( shaderStage ) {

        const codes = this.codes[ shaderStage ];

        let code = '';

        if ( codes !== undefined ) {

            for ( const nodeCode of codes ) {

                code += nodeCode.code + '\n';

            }

        }

        return code;

    }
getHash(): string
Code
getHash() {

        return this.vertexShader + this.fragmentShader + this.computeShader;

    }
setShaderStage(shaderStage: "compute" | "vertex" | "fragment" | "any"): void
Code
setShaderStage( shaderStage ) {

        this.shaderStage = shaderStage;

    }
getShaderStage(): "compute" | "vertex" | "fragment" | "any"
Code
getShaderStage() {

        return this.shaderStage;

    }
setBuildStage(buildStage: "setup" | "generate" | "analyze"): void
Code
setBuildStage( buildStage ) {

        this.buildStage = buildStage;

    }
getBuildStage(): "setup" | "generate" | "analyze"
Code
getBuildStage() {

        return this.buildStage;

    }
buildCode(): void
Code
buildCode() {

        console.warn( 'Abstract function.' );

    }
addSubBuild(subBuild: SubBuildNode): void
Code
addSubBuild( subBuild ) {

        this.subBuildLayers.push( subBuild );

    }
removeSubBuild(): SubBuildNode
Code
removeSubBuild() {

        return this.subBuildLayers.pop();

    }
getClosestSubBuild(data: any[] | Node | Set<any>): string
Code
getClosestSubBuild( data ) {

        let subBuilds;

        if ( data && data.isNode ) {

            if ( data.isShaderCallNodeInternal ) {

                subBuilds = data.shaderNode.subBuilds;

            } else if ( data.isStackNode ) {

                subBuilds = [ data.subBuild ];

            } else {

                subBuilds = this.getDataFromNode( data, 'any' ).subBuilds;

            }

        } else if ( data instanceof Set ) {

            subBuilds = [ ...data ];

        } else {

            subBuilds = data;

        }

        if ( ! subBuilds ) return null;

        const subBuildLayers = this.subBuildLayers;

        for ( let i = subBuilds.length - 1; i >= 0; i -- ) {

            const subBuild = subBuilds[ i ];

            if ( subBuildLayers.includes( subBuild ) ) {

                return subBuild;

            }

        }

        return null;

    }
getSubBuildOutput(node: Node): string
Code
getSubBuildOutput( node ) {

        return this.getSubBuildProperty( 'outputNode', node );

    }
getSubBuildProperty(property: string, node: Node): string
Code
getSubBuildProperty( property = '', node = null ) {

        let subBuild;

        if ( node !== null ) {

            subBuild = this.getClosestSubBuild( node );

        } else {

            subBuild = this.subBuildFn;

        }

        let result;

        if ( subBuild ) {

            result = property ? ( subBuild + '_' + property ) : subBuild;

        } else {

            result = property;

        }

        return result;

    }
build(): NodeBuilder
Code
build() {

        const { object, material, renderer } = this;

        if ( material !== null ) {

            let nodeMaterial = renderer.library.fromMaterial( material );

            if ( nodeMaterial === null ) {

                console.error( `NodeMaterial: Material "${ material.type }" is not compatible.` );

                nodeMaterial = new NodeMaterial();

            }

            nodeMaterial.build( this );

        } else {

            this.addFlow( 'compute', object );

        }

        // setup() -> stage 1: create possible new nodes and/or return an output reference node
        // analyze()   -> stage 2: analyze nodes to possible optimization and validation
        // generate()  -> stage 3: generate shader

        for ( const buildStage of defaultBuildStages ) {

            this.setBuildStage( buildStage );

            if ( this.context.vertex && this.context.vertex.isNode ) {

                this.flowNodeFromShaderStage( 'vertex', this.context.vertex );

            }

            for ( const shaderStage of shaderStages ) {

                this.setShaderStage( shaderStage );

                const flowNodes = this.flowNodes[ shaderStage ];

                for ( const node of flowNodes ) {

                    if ( buildStage === 'generate' ) {

                        this.flowNode( node );

                    } else {

                        node.build( this );

                    }

                }

            }

        }

        this.setBuildStage( null );
        this.setShaderStage( null );

        // stage 4: build code for a specific output

        this.buildCode();
        this.buildUpdateNodes();

        return this;

    }
getNodeUniform(uniformNode: NodeUniform, type: string): Uniform
Code
getNodeUniform( uniformNode, type ) {

        if ( type === 'float' || type === 'int' || type === 'uint' ) return new NumberNodeUniform( uniformNode );
        if ( type === 'vec2' || type === 'ivec2' || type === 'uvec2' ) return new Vector2NodeUniform( uniformNode );
        if ( type === 'vec3' || type === 'ivec3' || type === 'uvec3' ) return new Vector3NodeUniform( uniformNode );
        if ( type === 'vec4' || type === 'ivec4' || type === 'uvec4' ) return new Vector4NodeUniform( uniformNode );
        if ( type === 'color' ) return new ColorNodeUniform( uniformNode );
        if ( type === 'mat2' ) return new Matrix2NodeUniform( uniformNode );
        if ( type === 'mat3' ) return new Matrix3NodeUniform( uniformNode );
        if ( type === 'mat4' ) return new Matrix4NodeUniform( uniformNode );

        throw new Error( `Uniform "${type}" not declared.` );

    }
format(snippet: string, fromType: string, toType: string): string
Code
format( snippet, fromType, toType ) {

        fromType = this.getVectorType( fromType );
        toType = this.getVectorType( toType );

        if ( fromType === toType || toType === null || this.isReference( toType ) ) {

            return snippet;

        }

        const fromTypeLength = this.getTypeLength( fromType );
        const toTypeLength = this.getTypeLength( toType );

        if ( fromTypeLength === 16 && toTypeLength === 9 ) {

            return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xyz, ${ snippet }[ 1 ].xyz, ${ snippet }[ 2 ].xyz )`;

        }

        if ( fromTypeLength === 9 && toTypeLength === 4 ) {

            return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xy, ${ snippet }[ 1 ].xy )`;

        }


        if ( fromTypeLength > 4 ) { // fromType is matrix-like

            // @TODO: ignore for now

            return snippet;

        }

        if ( toTypeLength > 4 || toTypeLength === 0 ) { // toType is matrix-like or unknown

            // @TODO: ignore for now

            return snippet;

        }

        if ( fromTypeLength === toTypeLength ) {

            return `${ this.getType( toType ) }( ${ snippet } )`;

        }

        if ( fromTypeLength > toTypeLength ) {

            snippet = toType === 'bool' ? `all( ${ snippet } )` : `${ snippet }.${ 'xyz'.slice( 0, toTypeLength ) }`;

            return this.format( snippet, this.getTypeFromLength( toTypeLength, this.getComponentType( fromType ) ), toType );

        }

        if ( toTypeLength === 4 && fromTypeLength > 1 ) { // toType is vec4-like

            return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec3' ) }, 1.0 )`;

        }

        if ( fromTypeLength === 2 ) { // fromType is vec2-like and toType is vec3-like

            return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec2' ) }, 0.0 )`;

        }

        if ( fromTypeLength === 1 && toTypeLength > 1 && fromType !== this.getComponentType( toType ) ) { // fromType is float-like

            // convert a number value to vector type, e.g:
            // vec3( 1u ) -> vec3( float( 1u ) )

            snippet = `${ this.getType( this.getComponentType( toType ) ) }( ${ snippet } )`;

        }

        return `${ this.getType( toType ) }( ${ snippet } )`; // fromType is float-like

    }
getSignature(): string
Code
getSignature() {

        return `// Three.js r${ REVISION } - Node System\n`;

    }