UNPKG

three

Version:

JavaScript 3D library

41,904 lines • 1.07 MB
/**
 * @license
 * Copyright 2010-2026 Three.js Authors
 * SPDX-License-Identifier: MIT
 */
import { Color, Vector2, Vector3, Vector4, Matrix2, Matrix3, Matrix4, error, UnsignedIntType, IntType, RedFormat, RedIntegerFormat, DepthFormat, DepthStencilFormat, AlphaFormat, RGFormat, RGIntegerFormat, RGBFormat, RGBIntegerFormat, EventDispatcher, generateUUID, warn, WebGLCoordinateSystem, WebGPUCoordinateSystem, ColorManagement, SRGBTransfer, NoToneMapping, StaticDrawUsage, InterleavedBufferAttribute, InterleavedBuffer, DynamicDrawUsage, NoColorSpace, log as log$1, warnOnce, NormalBlending, SrcAlphaFactor, OneMinusSrcAlphaFactor, AddEquation, MaterialBlending, NoBlending, Sphere, BackSide, DoubleSide, Texture, Compatibility, LessCompare, LessEqualCompare, GreaterCompare, GreaterEqualCompare, NearestFilter, FramebufferTexture, LinearMipmapLinearFilter, DepthTexture, RenderTarget, Object3D, HalfFloatType, LinearMipMapLinearFilter, Plane, CubeTexture, CubeReflectionMapping, CubeRefractionMapping, TangentSpaceNormalMap, NoNormalPacking, NormalRGPacking, NormalGAPacking, ObjectSpaceNormalMap, RED_GREEN_RGTC2_Format, RG11_EAC_Format, DataTexture, RGBAFormat, FloatType, InstancedBufferAttribute, DataArrayTexture, RenderObjectRefreshType, Material, Mesh, OrthographicCamera, BufferGeometry, Float32BufferAttribute, BufferAttribute, UVMapping, LinearSRGBColorSpace, UnsignedInt248Type, VSMShadowMap, PCFShadowMap, LinearFilter, BasicShadowMap, CubeDepthTexture, BoxGeometry, Scene, CubeCamera, floorPowerOfTwo, ClampToEdgeWrapping } from './three.core.js';

/**
 * Possible shader stages.
 *
 * @property {string} VERTEX The vertex shader stage.
 * @property {string} FRAGMENT The fragment shader stage.
 */
const NodeShaderStage = {
	VERTEX: 'vertex',
	FRAGMENT: 'fragment'
};

/**
 * Update types of a node.
 *
 * @property {string} NONE The update method is not executed.
 * @property {string} FRAME The update method is executed per frame.
 * @property {string} RENDER The update method is executed per render. A frame might be produced by multiple render calls so this value allows more detailed updates than FRAME.
 * @property {string} OBJECT The update method is executed per {@link Object3D} that uses the node for rendering.
 */
const NodeUpdateType = {
	NONE: 'none',
	FRAME: 'frame',
	RENDER: 'render',
	OBJECT: 'object'
};

/**
 * Data types of a node.
 *
 * @property {string} BOOLEAN Boolean type.
 * @property {string} INTEGER Integer type.
 * @property {string} FLOAT Float type.
 * @property {string} VECTOR2 Two-dimensional vector type.
 * @property {string} VECTOR3 Three-dimensional vector type.
 * @property {string} VECTOR4 Four-dimensional vector type.
 * @property {string} MATRIX2 2x2 matrix type.
 * @property {string} MATRIX3 3x3 matrix type.
 * @property {string} MATRIX4 4x4 matrix type.
 */
const NodeType = {
	BOOLEAN: 'bool',
	INTEGER: 'int',
	FLOAT: 'float',
	VECTOR2: 'vec2',
	VECTOR3: 'vec3',
	VECTOR4: 'vec4',
	MATRIX2: 'mat2',
	MATRIX3: 'mat3',
	MATRIX4: 'mat4'
};

/**
 * Access types of a node. These are relevant for compute and storage usage.
 *
 * @property {string} READ_ONLY Read-only access
 * @property {string} WRITE_ONLY Write-only access.
 * @property {string} READ_WRITE Read and write access.
 */
const NodeAccess = {
	READ_ONLY: 'readOnly',
	WRITE_ONLY: 'writeOnly',
	READ_WRITE: 'readWrite',
};

const defaultShaderStages = [ 'fragment', 'vertex' ];
const defaultBuildStages = [ 'setup', 'analyze', 'generate' ];
const shaderStages = [ ...defaultShaderStages, 'compute' ];
const vectorComponents = [ 'x', 'y', 'z', 'w' ];

// Pre-compiled RegExp patterns for ignored files
const IGNORED_FILES = [
	/^StackTrace\.js$/,
	/^TSLCore\.js$/,
	/^.*Node\.js$/,
	/^three\.webgpu.*\.js$/
];

/**
 * Parses the stack trace and filters out ignored files.
 * Returns an array with function name, file, line, and column.
 */
function getFilteredStack( stack ) {

	// Pattern to extract function name, file, line, and column from different browsers
	// Chrome: "at functionName (file.js:1:2)" or "at file.js:1:2"
	// Firefox: "functionName@file.js:1:2"
	const regex = /(?:at\s+(.+?)\s+\()?(?:(.+?)@)?([^@\s()]+):(\d+):(\d+)/;

	return stack.split( '\n' )
		.map( line => {

			const match = line.match( regex );
			if ( ! match ) return null; // Skip if line format is invalid

			// Chrome: match[1], Firefox: match[2]
			const fn = match[ 1 ] || match[ 2 ] || '';
			const file = match[ 3 ].split( '?' )[ 0 ]; // Clean file name (Vite/HMR)
			const lineNum = parseInt( match[ 4 ], 10 );
			const column = parseInt( match[ 5 ], 10 );

			// Extract only the filename from full path
			const fileName = file.split( '/' ).pop();

			return {
				fn: fn,
				file: fileName,
				line: lineNum,
				column: column
			};

		} )
		.filter( frame => {

			// Only keep frames that are valid and not in the ignore list
			return frame && ! IGNORED_FILES.some( regex => regex.test( frame.file ) );

		} );

}

/**
 * Class representing a stack trace for debugging purposes.
 */
class StackTrace {

	/**
	 * Creates a StackTrace instance by capturing and filtering the current stack trace.
	 *
	 * @param {Error|string|null} stackMessage - An optional stack trace to use instead of capturing a new one.
	 */
	constructor( stackMessage = null ) {

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isStackTrace = true;

		/**
		 * The stack trace.
		 *
		 * @type {Array<{fn: string, file: string, line: number, column: number}>}
		 */
		this.stack = getFilteredStack( stackMessage ? stackMessage : new Error().stack );

	}

	/**
	 * Returns a formatted location string of the top stack frame.
	 *
	 * @returns {string} The formatted stack trace message.
	 */
	getLocation() {

		if ( this.stack.length === 0 ) {

			return '[Unknown location]';

		}

		const mainStack = this.stack[ 0 ];

		const fn = mainStack.fn;
		const fnName = fn ? `"${ fn }()" at ` : '';

		return `${fnName}"${mainStack.file}:${mainStack.line}"`; // :${mainStack.column}

	}

	/**
	 * Returns the full error message including the stack trace.
	 *
	 * @param {string} message - The error message.
	 * @returns {string} The full error message with stack trace.
	 */
	getError( message ) {

		if ( this.stack.length === 0 ) {

			return message;

		}

		// Output: "Error: message\n    at functionName (file.js:line:column)"
		const stackString = this.stack.map( frame => {

			const location = `${ frame.file }:${ frame.line }:${ frame.column }`;

			if ( frame.fn ) {

				return `    at ${ frame.fn } (${ location })`;

			}

			return `    at ${ location }`;

		} ).join( '\n' );

		return `${ message }\n${ stackString }`;

	}

}

// cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated.
// A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance.
// Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
// See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480
// https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
function cyrb53( value, seed = 0 ) {

	let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;

	if ( Array.isArray( value ) ) {

		for ( let i = 0, val; i < value.length; i ++ ) {

			val = value[ i ];
			h1 = Math.imul( h1 ^ val, 2654435761 );
			h2 = Math.imul( h2 ^ val, 1597334677 );

		}

	} else {

		for ( let i = 0, ch; i < value.length; i ++ ) {

			ch = value.charCodeAt( i );
			h1 = Math.imul( h1 ^ ch, 2654435761 );
			h2 = Math.imul( h2 ^ ch, 1597334677 );

		}

	}

	h1 = Math.imul( h1 ^ ( h1 >>> 16 ), 2246822507 );
	h1 ^= Math.imul( h2 ^ ( h2 >>> 13 ), 3266489909 );
	h2 = Math.imul( h2 ^ ( h2 >>> 16 ), 2246822507 );
	h2 ^= Math.imul( h1 ^ ( h1 >>> 13 ), 3266489909 );

	return 4294967296 * ( 2097151 & h2 ) + ( h1 >>> 0 );

}

/**
 * Computes a hash for the given string.
 *
 * @private
 * @method
 * @param {string} str - The string to be hashed.
 * @return {number} The hash.
 */
const hashString = ( str ) => cyrb53( str );

/**
 * Computes a hash for the given array.
 *
 * @private
 * @method
 * @param {Array<number>} array - The array to be hashed.
 * @return {number} The hash.
 */
const hashArray = ( array ) => cyrb53( array );

/**
 * Computes a hash for the given list of parameters.
 *
 * @private
 * @method
 * @param {...number} params - A list of parameters.
 * @return {number} The hash.
 */
const hash$1 = ( ...params ) => cyrb53( params );

const typeFromLength = /*@__PURE__*/ new Map( [
	[ 1, 'float' ],
	[ 2, 'vec2' ],
	[ 3, 'vec3' ],
	[ 4, 'vec4' ],
	[ 9, 'mat3' ],
	[ 16, 'mat4' ]
] );

const dataFromObject = /*@__PURE__*/ new WeakMap();

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

	return typeFromLength.get( length );

}

/**
 * Returns the typed array for the given data type.
 *
 * @private
 * @method
 * @param {string} type - The data type.
 * @return {TypedArray} The typed array.
 */
function getTypedArrayFromType( type ) {

	// Handle component type for vectors and matrices
	if ( /[iu]?vec\d/.test( type ) ) {

		// Handle int vectors
		if ( type.startsWith( 'ivec' ) ) return Int32Array;
		// Handle uint vectors
		if ( type.startsWith( 'uvec' ) ) return Uint32Array;
		// Default to float vectors
		return Float32Array;

	}

	// Handle matrices (always float)
	if ( /mat\d/.test( type ) ) return Float32Array;

	// Basic types
	if ( /float/.test( type ) ) return Float32Array;
	if ( /uint/.test( type ) ) return Uint32Array;
	if ( /int/.test( type ) ) return Int32Array;

	throw new Error( `THREE.NodeUtils: Unsupported type: ${type}` );

}

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

	if ( /float|int|uint|bool/.test( type ) ) return 1;
	if ( /vec2/.test( type ) ) return 2;
	if ( /vec3/.test( type ) ) return 3;
	if ( /vec4/.test( type ) ) return 4;
	if ( /mat2/.test( type ) ) return 4;
	if ( /mat3/.test( type ) ) return 9;
	if ( /mat4/.test( type ) ) return 16;

	error( `TSL: Unsupported type: ${ type }`, new StackTrace() );

}

/**
 * Returns the gpu memory length for the given data type in 4-byte elements.
 *
 * @private
 * @method
 * @param {string} type - The data type.
 * @return {number} The memory length in 4-byte elements.
 */
function getMemoryLengthFromType( type ) {

	if ( /float|int|uint|bool/.test( type ) ) return 1;
	if ( /vec2/.test( type ) ) return 2;
	if ( /vec3/.test( type ) ) return 3;
	if ( /vec4/.test( type ) ) return 4;
	if ( /mat2/.test( type ) ) return 4;
	if ( /mat3/.test( type ) ) return 12;
	if ( /mat4/.test( type ) ) return 16;

	error( `TSL: Unsupported type: ${ type }`, new StackTrace() );

}

/**
 * Returns the alignment requirement for the given data type in 4-byte elements.
 *
 * @private
 * @method
 * @param {string} type - The data type.
 * @return {number} The alignment requirement in 4-byte elements.
 */
function getAlignmentFromType( type ) {

	if ( /float|int|uint|bool/.test( type ) ) return 1;
	if ( /vec2/.test( type ) ) return 2;
	if ( /vec3/.test( type ) ) return 4;
	if ( /vec4/.test( type ) ) return 4;
	if ( /mat2/.test( type ) ) return 2;
	if ( /mat3/.test( type ) ) return 4;
	if ( /mat4/.test( type ) ) return 4;

	error( `TSL: Unsupported type: ${ type }`, new StackTrace() );

}

/**
 * Returns the data type for the given value.
 *
 * @private
 * @method
 * @param {any} value - The value.
 * @return {?string} The data type.
 */
function getValueType( value ) {

	if ( value === undefined || value === null ) return null;

	const typeOf = typeof value;

	if ( value.isNode === true ) {

		return 'node';

	} else if ( typeOf === 'number' ) {

		return 'float';

	} else if ( typeOf === 'boolean' ) {

		return 'bool';

	} else if ( typeOf === 'string' ) {

		return 'string';

	} else if ( typeOf === 'function' ) {

		return 'shader';

	} else if ( value.isVector2 === true ) {

		return 'vec2';

	} else if ( value.isVector3 === true ) {

		return 'vec3';

	} else if ( value.isVector4 === true ) {

		return 'vec4';

	} else if ( value.isMatrix2 === true ) {

		return 'mat2';

	} else if ( value.isMatrix3 === true ) {

		return 'mat3';

	} else if ( value.isMatrix4 === true ) {

		return 'mat4';

	} else if ( value.isColor === true ) {

		return 'color';

	} else if ( value instanceof ArrayBuffer ) {

		return 'ArrayBuffer';

	}

	return null;

}

/**
 * Returns the node data type for the given texture.
 *
 * @private
 * @method
 * @param {Texture} texture - The texture.
 * @return {string} The data type.
 */
function getTextureType( texture ) {

	if ( texture.isDepthTexture === true ) {

		return 'float';

	}

	const format = texture.format;

	let length;

	if ( format === RedFormat || format === RedIntegerFormat || format === DepthFormat || format === DepthStencilFormat || format === AlphaFormat ) {

		length = 1;

	} else if ( format === RGFormat || format === RGIntegerFormat ) {

		length = 2;

	} else if ( format === RGBFormat || format === RGBIntegerFormat ) {

		length = 3;

	} else {

		length = 4;

	}

	let componentType;

	if ( texture.type === UnsignedIntType ) {

		componentType = 'uint';

	} else if ( texture.type === IntType ) {

		componentType = 'int';

	} else {

		componentType = 'float';

	}

	if ( length === 1 ) return componentType;

	let baseType = getTypeFromLength( length );

	if ( componentType !== 'float' ) {

		baseType = componentType[ 0 ] + baseType;

	}

	return baseType;

}

/**
 * Returns the value/object for the given data type and parameters.
 *
 * @private
 * @method
 * @param {string} type - The given type.
 * @param {...any} params - A parameter list.
 * @return {any} The value/object.
 */
function getValueFromType( type, ...params ) {

	const last4 = type ? type.slice( -4 ) : undefined;

	if ( params.length === 1 ) { // ensure same behaviour as in NodeBuilder.format()

		if ( last4 === 'vec2' ) params = [ params[ 0 ], params[ 0 ] ];
		else if ( last4 === 'vec3' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ] ];
		else if ( last4 === 'vec4' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ], params[ 0 ] ];

	}

	if ( type === 'color' ) {

		return new Color( ...params );

	} else if ( last4 === 'vec2' ) {

		return new Vector2( ...params );

	} else if ( last4 === 'vec3' ) {

		return new Vector3( ...params );

	} else if ( last4 === 'vec4' ) {

		return new Vector4( ...params );

	} else if ( last4 === 'mat2' ) {

		return new Matrix2( ...params );

	} else if ( last4 === 'mat3' ) {

		return new Matrix3( ...params );

	} else if ( last4 === 'mat4' ) {

		return new Matrix4( ...params );

	} else if ( type === 'bool' ) {

		return params[ 0 ] || false;

	} else if ( ( type === 'float' ) || ( type === 'int' ) || ( type === 'uint' ) ) {

		return params[ 0 ] || 0;

	} else if ( type === 'string' ) {

		return params[ 0 ] || '';

	} else if ( type === 'ArrayBuffer' ) {

		return base64ToArrayBuffer( params[ 0 ] );

	}

	return null;

}

/**
 * Gets the object data that can be shared between different rendering steps.
 *
 * @private
 * @param {Object} object - The object to get the data for.
 * @return {Object} The object data.
 */
function getDataFromObject( object ) {

	let data = dataFromObject.get( object );

	if ( data === undefined ) {

		data = {};
		dataFromObject.set( object, data );

	}

	return data;

}

/**
 * Converts the given array buffer to a Base64 string.
 *
 * @private
 * @method
 * @param {ArrayBuffer} arrayBuffer - The array buffer.
 * @return {string} The Base64 string.
 */
function arrayBufferToBase64( arrayBuffer ) {

	let chars = '';

	const array = new Uint8Array( arrayBuffer );

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

		chars += String.fromCharCode( array[ i ] );

	}

	return btoa( chars );

}

/**
 * Merges the given node build contexts into a new context, the data of the source context takes precedence.
 * The node overrides of both contexts are merged as well, so the overrides of the target context still
 * apply unless the source context overrides the same node.
 *
 * @private
 * @method
 * @param {Object} target - The context to merge into, e.g. the outer context.
 * @param {Object} source - The context to merge, e.g. the inner context.
 * @return {Object} The merged context.
 */
function mergeContext( target, source ) {

	const context = { ...target, ...source };

	if ( target.overrideNodes !== undefined && source.overrideNodes !== undefined && target.overrideNodes !== source.overrideNodes ) {

		context.overrideNodes = new Map( [ ...target.overrideNodes, ...source.overrideNodes ] );

	}

	return context;

}

/**
 * Converts the given Base64 string to an array buffer.
 *
 * @private
 * @method
 * @param {string} base64 - The Base64 string.
 * @return {ArrayBuffer} The array buffer.
 */
function base64ToArrayBuffer( base64 ) {

	return Uint8Array.from( atob( base64 ), c => c.charCodeAt( 0 ) ).buffer;

}

function isArrayAsParameter( params ) {

	return ( params[ 0 ] !== undefined && params[ 0 ] !== null ) && ( params[ 0 ].isNode || Object.getPrototypeOf( params[ 0 ] ) !== Object.prototype );

}

var NodeUtils = /*#__PURE__*/Object.freeze({
	__proto__: null,
	arrayBufferToBase64: arrayBufferToBase64,
	base64ToArrayBuffer: base64ToArrayBuffer,
	getAlignmentFromType: getAlignmentFromType,
	getDataFromObject: getDataFromObject,
	getLengthFromType: getLengthFromType,
	getMemoryLengthFromType: getMemoryLengthFromType,
	getTextureType: getTextureType,
	getTypeFromLength: getTypeFromLength,
	getTypedArrayFromType: getTypedArrayFromType,
	getValueFromType: getValueFromType,
	getValueType: getValueType,
	hash: hash$1,
	hashArray: hashArray,
	hashString: hashString,
	isArrayAsParameter: isArrayAsParameter,
	mergeContext: mergeContext
});

const _parentBuildStage = {
	analyze: 'setup',
	generate: 'analyze'
};

let _nodeId = 0;

/**
 * Base class for all nodes.
 *
 * @augments EventDispatcher
 */
class Node extends EventDispatcher {

	static get type() {

		return 'Node';

	}

	/**
	 * Constructs a new node.
	 *
	 * @param {?string} nodeType - The node type.
	 */
	constructor( nodeType = null ) {

		super();

		/**
		 * The node type. This represents the result type of the node (e.g. `float` or `vec3`).
		 *
		 * @type {?string}
		 * @default null
		 */
		this.nodeType = nodeType;

		/**
		 * The update type of the node's {@link Node#update} method. Possible values are listed in {@link NodeUpdateType}.
		 *
		 * @type {string}
		 * @default 'none'
		 */
		this.updateType = NodeUpdateType.NONE;

		/**
		 * The update type of the node's {@link Node#updateBefore} method. Possible values are listed in {@link NodeUpdateType}.
		 *
		 * @type {string}
		 * @default 'none'
		 */
		this.updateBeforeType = NodeUpdateType.NONE;

		/**
		 * The update type of the node's {@link Node#updateAfter} method. Possible values are listed in {@link NodeUpdateType}.
		 *
		 * @type {string}
		 * @default 'none'
		 */
		this.updateAfterType = NodeUpdateType.NONE;

		/**
		 * The version of the node. The version automatically is increased when {@link Node#needsUpdate} is set to `true`.
		 *
		 * @type {number}
		 * @readonly
		 * @default 0
		 */
		this.version = 0;

		/**
		 * The name of the node.
		 *
		 * @type {string}
		 * @default ''
		 */
		this.name = '';

		/**
		 * Whether this node is global or not. This property is relevant for the internal
		 * node caching system. All nodes which should be declared just once should
		 * set this flag to `true` (a typical example is {@link AttributeNode}).
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.global = false;

		/**
		 * Create a list of parents for this node during the build process.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.parents = false;

		/**
		 * Whether this node can be assigned. It is generated as an expression,
		 * unless it is assigned, in which case it is declared as a variable where it was created.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.assignable = false;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isNode = true;

		// private

		this._inspectorNode = null;

		/**
		 * The cache key of this node.
		 *
		 * @private
		 * @type {?number}
		 * @default null
		 */
		this._cacheKey = null;

		/**
		 * The UUID of the node.
		 *
		 * @type {string}
		 * @default null
		 * @private
		 */
		this._uuid = null;

		/**
		 * The cache key's version.
		 *
		 * @private
		 * @type {number}
		 * @default 0
		 */
		this._cacheKeyVersion = 0;

		/**
		 * The unique ID of the node.
		 *
		 * @type {number}
		 * @readonly
		 */
		this.id = _nodeId ++;

		/**
		 * The stack trace of the node for debugging purposes.
		 *
		 * @type {?string}
		 * @default null
		 */
		this.stackTrace = null;

		if ( Node.captureStackTrace === true ) {

			this.stackTrace = new StackTrace();

		}

	}

	/**
	 * Whether this node allows caching its result in a temporary variable.
	 * Caching is enabled by default. Override this method to disable it.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {boolean} Whether temporary caching is allowed.
	 */
	isCacheable( /*builder*/ ) {

		return true;

	}

	/**
	 * Makes this node assignable and adds it to the current stack,
	 * so it can be declared where it was created if it is assigned later.
	 * Only use it with nodes that are not shared, see {@link Node#assignable}.
	 *
	 * @return {Node} A reference to this node.
	 */
	toAssignable() {

		this.assignable = true;

		return this.toStack();

	}

	/**
	 * Checks if this node is assignable without a forced declaration.
	 *
	 * @param {NodeBuilder} builder - The node builder.
	 * @returns {boolean} Whether this node is assignable without a forced declaration.
	 */
	isAssignable( builder ) {

		const data = builder.getDataFromNode( this );

		if ( data.forceDeclaration === true ) return false;

		return this.assignable;

	}

	/**
	 * Checks if this node is the target of an assignment.
	 *
	 * @param {NodeBuilder} builder - The node builder.
	 * @returns {boolean} Whether this node is assigned.
	 */
	isAssign( builder ) {

		const data = builder.getDataFromNode( this );

		return data.assign;

	}

	/**
	 * Declares the assignable node before the loops where it is used if it has no stack, and
	 * decides if a value that cannot be cached must be evaluated once at its declaration.
	 *
	 * @private
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Object} data - The node data.
	 * @param {Node} [valueNode=this] - The node that defines the value of the assignable node.
	 */
	_updateAssignable( builder, data, valueNode = this ) {

		const nodeLoop = builder.context.nodeLoop;

		if ( data.stack === undefined && builder.buildStage === 'setup' ) {

			// A node outside of the stacks of this build, e.g. created outside of a function, is declared
			// before the outermost loop where its value does not change, otherwise every iteration would repeat it.
			data.stack = builder.declareBeforeBlock( this );

		} else if ( this.assignable === true && nodeLoop && builder.buildStage === 'analyze' ) {

			if ( valueNode.isCacheable( builder ) === false && builder.isDeterministic( valueNode ) === false ) {

				// A value that cannot be cached, e.g. a function call, is evaluated once at its declaration
				// if it is used in a loop that runs after it, otherwise the loop would repeat it.
				const declarationIndex = builder.activeStacks.indexOf( data.stack );
				const loopIndex = builder.activeStacks.indexOf( builder.getDataFromNode( nodeLoop ).stack );

				if ( declarationIndex !== -1 && loopIndex >= declarationIndex ) data.forceDeclaration = true;

			}

		}

	}

	/**
	 * Whether `build()` declares this assignable node as a variable when it is assigned.
	 * Nodes that wrap an assignable value, like {@link VarNode}, declare it themselves.
	 *
	 * @private
	 * @return {boolean} Whether `build()` declares the assignable node.
	 */
	_declaresAssignable() {

		return true;

	}

	/**
	 * Declares a variable for this node in the current flow, initialized with the given snippet.
	 *
	 * @private
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} snippet - The initial value.
	 * @param {string} type - The variable type.
	 * @param {boolean} readOnly - Whether the variable is read-only.
	 * @param {string} property - The node data property that holds the variable.
	 * @return {string} The property name of the variable.
	 */
	_declareVariable( builder, snippet, type, readOnly, property ) {

		const nodeVar = builder.getVarFromNode( this, null, type, undefined, readOnly, true, property );
		const propertyName = builder.getPropertyName( nodeVar );
		const declarationPrefix = readOnly
			? builder.generateLetStatement( nodeVar.type, propertyName, nodeVar.count )
			: builder.generateVarStatement( nodeVar.type, propertyName, nodeVar.count );

		builder.addLineFlowCode( `${ declarationPrefix } = ${ snippet }`, this );

		return propertyName;

	}

	/**
	 * Whether this assignable node must be declared as a variable, because it is assigned
	 * or must be evaluated once at its declaration.
	 *
	 * @private
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Object} nodeData - The node data.
	 * @return {boolean} Whether the assignable node is a variable.
	 */
	_isVariable( builder, nodeData ) {

		return ( nodeData.assign === true || nodeData.forceDeclaration === true ) && this.getNodeType( builder ) !== 'void';

	}

	/**
	 * Generates an assignable node as a variable. The variable is declared the first
	 * time it is generated, which is its stack position, and then referenced by name.
	 *
	 * @private
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Object} nodeData - The node data.
	 * @param {?string} output - The output type.
	 * @return {string} The generated shader string.
	 */
	_buildVariable( builder, nodeData, output ) {

		const nodeType = this.getNodeType( builder );
		const type = builder.getVectorType( nodeType );

		if ( nodeData.propertyName === undefined ) {

			const snippet = this.generate.length < 2
				? builder.format( this.generate( builder ) || '', nodeType, type )
				: this.generate( builder, type ) || '';

			nodeData.propertyName = this._declareVariable( builder, snippet, type, false, 'variable' );

		}

		return builder.format( nodeData.propertyName, type, output );

	}

	/**
	 * Whether the generated value is a variable declared in the scope of the function,
	 * so it keeps its value after the block where it was generated, e.g. a {@link VarNode}.
	 *
	 * @private
	 * @return {boolean} Whether the generated value is a variable of the function scope.
	 */
	_isFunctionScope() {

		return false;

	}

	/**
	 * A generated value is only visible in the block where it was declared and in its inner blocks.
	 * A variable of the function scope is also visible in a later block of the same parent and guard,
	 * e.g. a second `if ( c )` of an immutable condition `c`, since that block only runs if its block ran.
	 * This method discards the generated value if it is not visible in the current block.
	 *
	 * @private
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Object} nodeData - The node data.
	 */
	_checkFlowBlock( builder, nodeData ) {

		if ( nodeData.flowBlock !== undefined ) {

			const { guard, parent } = nodeData.flowBlock;
			const isFunctionScope = guard !== undefined && this._isFunctionScope();

			let flowBlock = builder.flowBlock;

			while ( flowBlock !== null && flowBlock !== nodeData.flowBlock ) {

				if ( isFunctionScope && flowBlock.guard === guard && flowBlock.parent === parent ) break;

				flowBlock = flowBlock.parent;

			}

			if ( flowBlock === null ) {

				nodeData.flowBlock = undefined;
				nodeData.propertyName = undefined;
				nodeData.snippet = undefined;
				nodeData.generated = undefined;

			}

		}

	}

	/**
	 * Keeps the block where a value was generated, so it is only reused where it is visible.
	 * A global node is a declaration visible in any block, unless it emitted code in this block.
	 *
	 * @private
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Object} nodeData - The node data.
	 * @param {boolean} isCached - Whether the value was generated before.
	 * @param {number} flowCodeLength - The length of the flow code before the value was generated.
	 */
	_setFlowBlock( builder, nodeData, isCached, flowCodeLength ) {

		const isLocal = this.isGlobal( builder ) === false || builder.flow.code.length !== flowCodeLength;

		if ( isCached === false && ( nodeData.propertyName !== undefined || nodeData.snippet !== undefined ) && isLocal && builder.flowBlock !== null ) {

			nodeData.flowBlock = builder.flowBlock;

		}

	}

	/**
	 * Set this property to `true` when the node should be regenerated.
	 *
	 * @type {boolean}
	 * @default false
	 * @param {boolean} value
	 */
	set needsUpdate( value ) {

		if ( value === true ) {

			this.version ++;

		}

	}

	/**
	 * The UUID of the node.
	 *
	 * @type {string}
	 * @readonly
	 */
	get uuid() {

		if ( this._uuid === null ) {

			this._uuid = generateUUID();

		}

		return this._uuid;

	}

	/**
	 * The type of the class. The value is usually the constructor name.
	 *
	 * @type {string}
 	 * @readonly
	 */
	get type() {

		return this.constructor.type;

	}

	/**
	 * Convenient method for defining {@link Node#update}.
	 *
	 * @param {Function} callback - The update method.
	 * @param {string} updateType - The update type.
	 * @return {Node} A reference to this node.
	 */
	onUpdate( callback, updateType ) {

		this.updateType = updateType;
		this.update = callback.bind( this );

		return this;

	}

	/**
	 * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but
	 * this method automatically sets the update type to `FRAME`.
	 *
	 * @param {Function} callback - The update method.
	 * @return {Node} A reference to this node.
	 */
	onFrameUpdate( callback ) {

		return this.onUpdate( callback, NodeUpdateType.FRAME );

	}

	/**
	 * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but
	 * this method automatically sets the update type to `RENDER`.
	 *
	 * @param {Function} callback - The update method.
	 * @return {Node} A reference to this node.
	 */
	onRenderUpdate( callback ) {

		return this.onUpdate( callback, NodeUpdateType.RENDER );

	}

	/**
	 * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but
	 * this method automatically sets the update type to `OBJECT`.
	 *
	 * @param {Function} callback - The update method.
	 * @return {Node} A reference to this node.
	 */
	onObjectUpdate( callback ) {

		return this.onUpdate( callback, NodeUpdateType.OBJECT );

	}

	/**
	 * Convenient method for defining {@link Node#updateReference}.
	 *
	 * @param {Function} callback - The update method.
	 * @return {Node} A reference to this node.
	 */
	onReference( callback ) {

		this.updateReference = callback.bind( this );

		return this;

	}

	/**
	 * Nodes might refer to other objects like materials. This method allows to dynamically update the reference
	 * to such objects based on a given state (e.g. the current node frame or builder).
	 *
	 * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type.
	 * @return {any} The updated reference.
	 */
	updateReference( /*state*/ ) {

		return this;

	}

	/**
	 * By default this method returns the value of the {@link Node#global} flag. This method
	 * can be overwritten in derived classes if an analytical way is required to determine the
	 * global cache referring to the current shader-stage.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {boolean} Whether this node is global or not.
	 */
	isGlobal( /*builder*/ ) {

		return this.global;

	}

	/**
	 * Returns the child nodes of this node.
	 *
	 * @return {Array<Node>} The child nodes.
	 */
	getChildren() {

		const children = this._getChildren();
		const nodes = [];

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

			nodes.push( children[ i ].childNode );

		}

		return nodes;

	}

	/**
	 * Calling this method dispatches the `dispose` event. This event can be used
	 * to register event listeners for clean up tasks.
	 */
	dispose() {

		this.dispatchEvent( { type: 'dispose' } );

	}

	/**
	 * Callback for {@link Node#traverse}.
	 *
	 * @callback traverseCallback
	 * @param {Node} node - The current node.
	 */

	/**
	 * Can be used to traverse through the node's hierarchy.
	 *
	 * @param {traverseCallback} callback - A callback that is executed per node.
	 */
	traverse( callback ) {

		callback( this );

		for ( const childNode of this.getChildren() ) {

			childNode.traverse( callback );

		}

	}

	/**
	 * Returns the child nodes of this node.
	 *
	 * @private
	 * @param {Set<Node>} [ignores=new Set()] - A set of nodes to ignore during the search to avoid circular references.
	 * @returns {Array<Object>} An array of objects describing the child nodes.
	 */
	_getChildren( ignores = new Set() ) {

		const children = [];

		// avoid circular references
		ignores.add( this );

		const properties = Object.getOwnPropertyNames( this );

		for ( let p = 0; p < properties.length; p ++ ) {

			const property = properties[ p ];

			// Ignore private properties.
			if ( property.charCodeAt( 0 ) === 95 ) continue;

			const object = this[ property ];

			// Ignore primitives and ignored nodes.
			if ( object === null || ( typeof object !== 'object' && typeof object !== 'function' ) || ignores.has( object ) ) continue;

			if ( Array.isArray( object ) === true ) {

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

					const child = object[ i ];

					if ( child && child.isNode === true ) {

						children.push( { property, index: i, childNode: child } );

					}

				}

			} else if ( object && object.isNode === true ) {

				children.push( { property, childNode: object } );

			} else if ( object && Object.getPrototypeOf( object ) === Object.prototype ) {

				for ( const subProperty in object ) {

					// Ignore private sub-properties.
					if ( subProperty.startsWith( '_' ) === true ) continue;

					const child = object[ subProperty ];

					if ( child && child.isNode === true ) {

						children.push( { property, index: subProperty, childNode: child } );

					}

				}

			}

		}

		//

		return children;

	}

	/**
	 * Returns the cache key for this node.
	 *
	 * @param {boolean} [force=false] - When set to `true`, a recomputation of the cache key is forced.
	 * @param {Set<Node>} [ignores=null] - A set of nodes to ignore during the computation of the cache key.
	 * @return {number} The cache key of the node.
	 */
	getCacheKey( force = false, ignores = null ) {

		force = force || this.version !== this._cacheKeyVersion;

		if ( force === true || this._cacheKey === null ) {

			if ( ignores === null ) ignores = new Set();

			//

			const values = [];

			for ( const { property, childNode } of this._getChildren( ignores ) ) {

				values.push( hashString( property.slice( 0, -4 ) ), childNode.getCacheKey( force, ignores ) );

			}

			//

			this._cacheKey = hash$1( hashArray( values ), this.customCacheKey() );
			this._cacheKeyVersion = this.version;

		}

		return this._cacheKey;

	}

	/**
	 * Generate a custom cache key for this node.
	 *
	 * @return {number} The cache key of the node.
	 */
	customCacheKey() {

		return this.id;

	}

	/**
	 * Returns the references to this node which is by default `this`.
	 *
	 * @return {Node} A reference to this node.
	 */
	getScope() {

		return this;

	}

	/**
	 * Returns the hash of the node which is used to identify the node. By default it's
	 * the {@link Node#uuid} however derived node classes might have to overwrite this method
	 * depending on their implementation.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The hash.
	 */
	getHash( /*builder*/ ) {

		return String( this.id );

	}

	/**
	 * Returns the update type of {@link Node#update}.
	 *
	 * @param {NodeFrame} [frame] - The current node frame.
	 * @return {NodeUpdateType} The update type.
	 */
	getUpdateType( /*frame*/ ) {

		return this.updateType;

	}

	/**
	 * Returns the update type of {@link Node#updateBefore}.
	 *
	 * @param {NodeFrame} [frame] - The current node frame.
	 * @return {NodeUpdateType} The update type.
	 */
	getUpdateBeforeType( /*frame*/ ) {

		return this.updateBeforeType;

	}

	/**
	 * Returns the update type of {@link Node#updateAfter}.
	 *
	 * @param {NodeFrame} [frame] - The current node frame.
	 * @return {NodeUpdateType} The update type.
	 */
	getUpdateAfterType( /*frame*/ ) {

		return this.updateAfterType;

	}

	/**
	 * Certain types are composed of multiple elements. For example a `vec3`
	 * is composed of three `float` values. This method returns the type of
	 * these elements.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The type of the node.
	 */
	getElementType( builder ) {

		const type = this.getNodeType( builder );
		const elementType = builder.getElementType( type );

		return elementType;

	}

	/**
	 * Returns the node member type for the given name.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} name - The name of the member.
	 * @return {string} The type of the node.
	 */
	getMemberType( /*builder, name*/ ) {

		return 'void';

	}

	/**
	 * Returns the node's type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} [output=null] - The output of the node.
	 * @return {string} The type of the node.
	 */
	getNodeType( builder, output = null ) {

		const nodeData = builder.getDataFromNode( this );

		let type;

		if ( output !== null ) {

			nodeData.typeFromOutput = nodeData.typeFromOutput || {};

			type = nodeData.typeFromOutput[ output ];

			if ( type === undefined ) {

				type = this.generateNodeType( builder, output );

				nodeData.typeFromOutput[ output ] = type;

			}

		} else {

			type = nodeData.type;

			if ( type === undefined ) {

				type = this.generateNodeType( builder );

				nodeData.type = type;

			}

		}

		return type;

	}

	/**
	 * Returns the node's type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} [output=null] - The output of the node.
	 * @return {string} The type of the node.
	 */
	generateNodeType( builder, output = null ) {

		const nodeProperties = builder.getNodeProperties( this );

		if ( nodeProperties.outputNode ) {

			return nodeProperties.outputNode.getNodeType( builder, output );

		}

		return this.nodeType;

	}

	/**
	 * This method is used during the build process of a node and ensures
	 * equal nodes are not built multiple times but just once. For example if
	 * `attribute( 'uv' )` is used multiple times by the user, the build
	 * process makes sure to process just the first node. It also handles
	 * node overrides if an override context is set.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node} The shared node if possible. Otherwise `this` is returned.
	 */
	getShared( builder ) {

		const hash = this.getHash( builder );
		const nodeFromHash = builder.getNodeFromHash( hash );

		let sharedNode = null;

		if ( nodeFromHash && nodeFromHash !== this ) {

			sharedNode = nodeFromHash;

		} else if ( builder.context.overrideNodes ) {

			const callback = builder.context.overrideNodes.get( this );

			if ( callback ) {

				// the overrides are cached per callback, nested contexts can override the same node with different nodes

				const nodeData = builder.getDataFromNode( this );
				const overrides = nodeData.overrides || ( nodeData.overrides = new Map() );

				if ( overrides.has( callback ) === false ) {

					// avoid recursive override calls

					overrides.set( callback, null );

					// cancel the override for use the same node inside the callback

					sharedNode = callback( builder ).overrideNode( this, null );

					overrides.set( callback, sharedNode );

				} else {

					sharedNode = overrides.get( callback );

				}

			}

		}

		return sharedNode || this;

	}

	/**
	 * Returns the number of elements in the node array.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {?number} The number of elements in the node array.
	 */
	getArrayCount( /*builder*/ ) {

		return null;

	}

	/**
	 * Represents the setup stage which is the first step of the build process, see {@link Node#build} method.
	 * This method is often overwritten in derived modules to prepare the node which is used as a node's output/result.
	 * If an output node is prepared, then it must be returned in the `return` statement of the derived module's setup function.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {?Node} The output node.
	 */
	setup( builder ) {

		const nodeProperties = builder.getNodeProperties( this );

		let index = 0;

		for ( const childNode of this.getChildren() ) {

			nodeProperties[ 'node' + index ++ ] = childNode;

		}

		// return a outputNode if exists or null

		return nodeProperties.outputNode || null;

	}

	/**
	 * Represents the analyze stage which is the second step of the build process, see {@link Node#build} method.
	 * This stage analyzes the node hierarchy and ensures descendent nodes are built.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {?Node} output - The target output node.
	 */
	analyze( builder, output = null ) {

		const usageCount = builder.increaseUsage( this );

		if ( this.parents === true ) {

			const nodeData = builder.getDataFromNode( this, 'any' );
			nodeData.stages = nodeData.stages || {};
			nodeData.stages[ builder.shaderStage ] = nodeData.stages[ builder.shaderStage ] || [];
			nodeData.stages[ builder.shaderStage ].push( output );

		}

		if ( usageCount === 1 ) {

			// node flow children

			const nodeProperties = builder.getNodeProperties( this );

			for ( const childNode of Object.values( nodeProperties ) ) {

				if ( childNode && childNode.isNode === true ) {

					childNode.build( builder, this );

				}

			}

		}

	}

	/**
	 * Represents the generate stage which is the third step of the build process, see {@link Node#build} method.
	 * This state builds the output node and returns the resulting shader string.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {?string} [output] - Can be used to define the output type.
	 * @return {?string} The generated shader string.
	 */
	generate( builder, output ) {

		const { outputNode } = builder.getNodeProperties( this );

		if ( outputNode && outputNode.isNode === true ) {

			return outputNode.build( builder, output );

		}

	}

	/**
	 * The method can be implemented to update the node's internal state before it is used to render an object.
	 * The {@link Node#updateBeforeType} property defines how often the update is executed.
	 *
	 * @abstract
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching).
	 */
	updateBefore( /*frame*/ ) {

		warn( 'Abstract function.' );

	}

	/**
	 * The method can be implemented to update the node's internal state after it was used to render an object.
	 * The {@link Node#updateAfterType} property defines how often the update is executed.
	 *
	 * @abstract
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching).
	 */
	updateAfter( /*frame*/ ) {

		warn( 'Abstract function.' );

	}

	/**
	 * The method can be implemented to update the node's internal state when it is used to render an object.
	 * The {@link Node#updateType} property defines how often the update is executed.
	 *
	 * @abstract
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching).
	 */
	update( /*frame*/ ) {

		warn( 'Abstract function.' );

	}

	/**
	 * Registers the inspector node added with `toInspector()` in the builder, so it is updated every frame.
	 * Nodes that override `build()` without always calling `super.build()` must call this method.
	 *
	 * @private
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	_setupInspector( builder ) {

		if ( this._inspectorNode !== null && builder.buildStage === 'setup' ) {

			builder.addNode( this._inspectorNode );

		}

	}

	/**
	 * This method performs the build of a node. The behavior and return value depend on the current build stage:
	 * - **setup**: Prepares the node and its children for the build process. This process can also create new nodes. Returns the node itself or a variant.
	 * - **analyze**: Analyzes the node hierarchy for optimizations in the code generation stage. Returns `null`.
	 * - **generate**: Generates the shader code for the node. Returns the generated shader string.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {?(string|Node)} [output=null] - Can be used to define the output type.
	 * @return {?(Node|string)} The result of the build process, depending on the build stage.
	 */
	build( builder, output = null ) {

		this._setupInspector( builder );

		const refNode = this.getShared( builder );

		if ( this !== refNode ) {

			return refNode.build( builder, output );

		}

		//

		const nodeData = builder.getDataFromNode( this );

		if ( this.assignable === true && this._declaresAssignable() ) this._updateAssignable( builder, nodeData );

		nodeData.buildStages = nodeData.buildStages || { setup: false, analyze: false, generate: false };
		nodeData.buildStages[ builder.buildStage ] = true;

		const parentBuildStage = _parentBuildStage[ builder.buildStage ];

		if ( parentBuildStage && nodeData.buildStages[ parentBuildStage ] !== true ) {

			// force parent build stage (setup or analyze)

			const previousBuildStage = builder.getBuildStage();

			builder.setBuildStage( parentBuildStage );

			this.build( builder );

			builder.setBuildStage( previousBuildStage );

		}

		//

		builder.addChain( this );

		/* Build stages expected results:
			- "setup"		-> Node
			- "analyze"		-> null
			- "generate"	-> String
		*/
		let result = null;

		const buildStage = builder.getBuildStage();

		if ( buildStage === 'setup' ) {

			builder.addNode( this );

			this.updateReference( builder );

			const properties = builder.getNodeProperties( this );

			if ( properties.initialized !== true ) {

				//const stackNodesBeforeSetup = builder.stack.nodes.length;

				properties.initialized = true;
				properties.outputNode = this.setup( builder ) || properties.outputNode || null;

				/*if ( isNodeOutput && builder.stack.nodes.length !== stackNodesBeforeSetup ) {

					// !! no outputNode !!
					//outputNode = builder.stack;

				}*/

				for ( const childNode of Object.values( properties ) ) {

					if ( childNode && childNode.isNode === true ) {

						if ( childNode.parents === true ) {

							const childProperties = builder.getNodeProperties( childNode );
							childProperties.parents = childProperties.parents || [];
							childProperties.parents.push( this );

						}

						childNode.build( builder );

					}

				}

				builder.addSequentialNode( this );

			}

			result = properties.outputNode;

		} else if ( buildStage === 'analyze' ) {

			this.analyze( builder, output );

		} else if ( buildStage === 'generate' ) {

			this._checkFlowBlock( builder, nodeData );

			const isCached = nodeData.propertyName !== undefined || nodeData.snippet !== undefined;
			const flowCodeLength = builder.flow.code.length;

			// References must be generated directly, even if a cached value exists.
			const allowedCache = this.isCacheable( builder ) && builder.isReference( output ) === false;
			const type = allowedCache ? builder.getVectorType( this.getNodeType( builder, output ) ) : null;
			const cacheResult = allowedCache && type !== 'void' && output !== 'void' && nodeData.usageCount > 1;
			const generateOutput = cacheResult ? type : output;

			if ( this.assignable === true && this._declaresAssignable() && this._isVariable( builder, nodeData ) ) {

				result = this._buildVariable( builder, nodeData, output );

			} else if ( allowedCache && nodeData.propertyName !== undefined ) {

				result = builder.format( nodeData.propertyName, type, output );

			} else {

				// If generate has just one argument, it means the output type is not required.
				// This means that the node does not handle output conversions internally,
				// so the value is stored in a cache and the builder handles the conversion
				// for all requested output types.

				const isGenerateOnce = this.generate.length < 2;

				if ( isGenerateOnce ) {

					const type = this.getNodeType( builder );

					result = nodeData.snippet;

					if ( result === undefined ) {

						if ( nodeData.generated === undefined ) {

							nodeData.generated = true;

							result = this.generate( builder ) || '';

							nodeData.snippet = result;

						} else {

							warn( 'Node: Recursion detected.', this );

							result = '/* Recursion detected. */';

						}

					}

					result = builder.format( result, type, generateOutput );

				} else {

					result = this.generate( builder, generateOutput ) || '';

				}

				if ( result === '' && generateOutput !== null && generateOutput !== 'void' && generateOutput !== 'OutputType' ) {

					// if no snippet is generated, return a default value

					error( `TSL: Invalid generated code, expected a "${ generateOutput }".` );

					result = builder.generateConst( generateOutput );

				}

				if ( cacheResult ) {

					// Use a dedicated property, the node may already own a variable.
					const propertyName = this._declareVariable( builder, result, type, nodeData.assign !== true, 'cacheVariable' );

					nodeData.snippet = result;
					nodeData.propertyName = propertyName;

					result = builder.format( propertyName, type, output );

				}

			}

			this._setFlowBlock( builder, nodeData, isCached, flowCodeLength );

		}

		builder.removeChain( this );

		return result;

	}

	/**
	 * Returns the child nodes as a JSON object.
	 *
	 * @return {Generator<Object>} An iterable list of serialized child objects as JSON.
	 */
	getSerializeChildren() {

		return this._getChildren();

	}

	/**
	 * Serializes the node to JSON.
	 *
	 * @param {Object} json - The output JSON object.
	 */
	serialize( json ) {

		const nodeChildren = this.getSerializeChildren();

		const inputNodes = {};

		for ( const { property, index, childNode } of nodeChildren ) {

			if ( index !== undefined ) {

				if ( inputNodes[ property ] === undefined ) {

					inputNodes[ property ] = Number.isInteger( index ) ? [] : {};

				}

				inputNodes[ property ][ index ] = childNode.toJSON( json.meta ).uuid;

			} else {

				inputNodes[ property ] = childNode.toJSON( json.meta ).uuid;

			}

		}

		if ( Object.keys( inputNodes ).length > 0 ) {

			json.inputNodes = inputNodes;

		}

	}

	/**
	 * Deserializes the node from the given JSON.
	 *
	 * @param {Object} json - The JSON object.
	 */
	deserialize( json ) {

		if ( json.inputNodes !== undefined ) {

			const nodes = json.meta.nodes;

			for ( const property in json.inputNodes ) {

				if ( Array.isArray( json.inputNodes[ property ] ) ) {

					const inputArray = [];

					for ( const uuid of json.inputNodes[ property ] ) {

						inputArray.push( nodes[ uuid ] );

					}

					this[ property ] = inputArray;

				} else if ( typeof json.inputNodes[ property ] === 'object' ) {

					const inputObject = {};

					for ( const subProperty in json.inputNodes[ property ] ) {

						const uuid = json.inputNodes[ property ][ subProperty ];

						inputObject[ subProperty ] = nodes[ uuid ];

					}

					this[ property ] = inputObject;

				} else {

					const uuid = json.inputNodes[ property ];

					this[ property ] = nodes[ uuid ];

				}

			}

		}

	}

	/**
	 * Serializes the node into the three.js JSON Object/Scene format.
	 *
	 * @param {?Object} meta - An optional JSON object that already holds serialized data from other scene objects.
	 * @return {Object} The serialized node.
	 */
	toJSON( meta ) {

		const { uuid, type } = this;
		const isRoot = ( meta === undefined || typeof meta === 'string' );

		if ( isRoot ) {

			meta = {
				textures: {},
				images: {},
				nodes: {}
			};

		}

		// serialize

		let data = meta.nodes[ uuid ];

		if ( data === undefined ) {

			data = {
				uuid,
				type,
				meta,
				metadata: {
					version: 4.7,
					type: 'Node',
					generator: 'Node.toJSON'
				}
			};

			if ( isRoot !== true ) meta.nodes[ data.uuid ] = data;

			this.serialize( data );

			delete data.meta;

		}

		// TODO: Copied from Object3D.toJSON

		function extractFromCache( cache ) {

			const values = [];

			for ( const key in cache ) {

				const data = cache[ key ];
				delete data.metadata;
				values.push( data );

			}

			return values;

		}

		if ( isRoot ) {

			const textures = extractFromCache( meta.textures );
			const images = extractFromCache( meta.images );
			const nodes = extractFromCache( meta.nodes );

			if ( textures.length > 0 ) data.textures = textures;
			if ( images.length > 0 ) data.images = images;
			if ( nodes.length > 0 ) data.nodes = nodes;

		}

		return data;

	}

}

/**
 * Enables or disables the automatic capturing of stack traces for nodes.
 *
 * @type {boolean}
 * @default false
 */
Node.captureStackTrace = false;

/**
 * Base class for representing element access on an array-like
 * node data structures.
 *
 * @augments Node
 */
class ArrayElementNode extends Node {

	static get type() {

		return 'ArrayElementNode';

	}

	/**
	 * Constructs an array element node.
	 *
	 * @param {Node} node - The array-like node.
	 * @param {Node} indexNode - The index node that defines the element access.
	 */
	constructor( node, indexNode ) {

		super();

		/**
		 * The array-like node.
		 *
		 * @type {Node}
		 */
		this.node = node;

		/**
		 * The index node that defines the element access.
		 *
		 * @type {Node}
		 */
		this.indexNode = indexNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isArrayElementNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Returns the scope of the array-like node so assignments to elements
	 * mark the underlying value as mutable.
	 *
	 * @return {Node} The scope of the node.
	 */
	getScope() {

		return this.node.getScope();

	}

	/**
	 * This method is overwritten since the node type is inferred from the array-like node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		return this.node.getElementType( builder );

	}

	/**
	 * This method is overwritten since the member type is inferred from the array-like node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} name - The member name.
	 * @return {string} The member type.
	 */
	getMemberType( builder, name ) {

		return this.node.getMemberType( builder, name );

	}

	generate( builder ) {

		const indexType = this.indexNode.getNodeType( builder );

		const nodeSnippet = this.node.build( builder );
		const indexSnippet = this.indexNode.build( builder, ! builder.isVector( indexType ) && builder.isInteger( indexType ) ? indexType : 'uint' );

		return `${ nodeSnippet }[ ${ indexSnippet } ]`;

	}

}

/**
 * This module is part of the TSL core and usually not used in app level code.
 * It represents a convert operation during the shader generation process
 * meaning it converts the data type of a node to a target data type.
 *
 * @augments Node
 */
class ConvertNode extends Node {

	static get type() {

		return 'ConvertNode';

	}

	/**
	 * Constructs a new convert node.
	 *
	 * @param {Node} node - The node which type should be converted.
	 * @param {string} convertTo - The target node type. Multiple types can be defined by separating them with a `|` sign.
	 */
	constructor( node, convertTo ) {

		super();

		/**
		 * The node which type should be converted.
		 *
		 * @type {Node}
		 */
		this.node = node;

		/**
		 * The target node type. Multiple types can be defined by separating them with a `|` sign.
		 *
		 * @type {string}
		 */
		this.convertTo = convertTo;

	}

	/**
	 * This method is overwritten since the implementation tries to infer the best
	 * matching type from the {@link ConvertNode#convertTo} property.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		const requestType = this.node.getNodeType( builder );

		let convertTo = null;

		for ( const overloadingType of this.convertTo.split( '|' ) ) {

			if ( convertTo === null || builder.getTypeLength( requestType ) === builder.getTypeLength( overloadingType ) ) {

				convertTo = overloadingType;

			}

		}

		return convertTo;

	}

	serialize( data ) {

		super.serialize( data );

		data.convertTo = this.convertTo;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.convertTo = data.convertTo;

	}

	generate( builder, output ) {

		const node = this.node;
		const type = this.getNodeType( builder );

		const snippet = node.build( builder, type );

		return builder.format( snippet, type, output );

	}

}

/**
 * This module is part of the TSL core and usually not used in app level code.
 * It represents a join operation during the shader generation process.
 * For example in can compose/join two single floats into a `vec2` type.
 *
 * @augments Node
 */
class JoinNode extends Node {

	static get type() {

		return 'JoinNode';

	}

	/**
	 * Constructs a new join node.
	 *
	 * @param {Array<Node>} nodes - An array of nodes that should be joined.
	 * @param {?string} [nodeType=null] - The node type.
	 */
	constructor( nodes = [], nodeType = null ) {

		super( nodeType );

		/**
		 * An array of nodes that should be joined.
		 *
		 * @type {Array<Node>}
		 */
		this.nodes = nodes;

	}

	/**
	 * This method is overwritten since the node type must be inferred from the
	 * joined data length if not explicitly defined.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		if ( this.nodeType !== null ) {

			return builder.getVectorType( this.nodeType );

		}

		return builder.getTypeFromLength( this.nodes.reduce( ( count, cur ) => count + builder.getTypeLength( cur.getNodeType( builder ) ), 0 ) );

	}

	generate( builder, output ) {

		const type = this.getNodeType( builder );
		const maxLength = builder.getTypeLength( type );

		const nodes = this.nodes;

		const primitiveType = builder.getComponentType( type );

		const snippetValues = [];

		let length = 0;

		for ( const input of nodes ) {

			if ( length >= maxLength ) {

				error( `TSL: Length of parameters exceeds maximum length of function '${ type }()' type.`, this.stackTrace );
				break;

			}

			let inputType = input.getNodeType( builder );
			let inputTypeLength = builder.getTypeLength( inputType );
			let inputSnippet;

			if ( length + inputTypeLength > maxLength ) {

				error( `TSL: Length of '${ type }()' data exceeds maximum length of output type.`, this.stackTrace );

				inputTypeLength = maxLength - length;
				inputType = builder.getTypeFromLength( inputTypeLength );

			}

			length += inputTypeLength;
			inputSnippet = input.build( builder, inputType );

			const inputPrimitiveType = builder.getComponentType( inputType );

			if ( inputPrimitiveType !== primitiveType ) {

				const targetType = builder.getTypeFromLength( inputTypeLength, primitiveType );

				inputSnippet = builder.format( inputSnippet, inputType, targetType );

			}

			snippetValues.push( inputSnippet );

		}

		const snippet = `${ builder.getType( type ) }( ${ snippetValues.join( ', ' ) } )`;

		return builder.format( snippet, type, output );

	}

}

const _stringVectorComponents = vectorComponents.join( '' );

/**
 * This module is part of the TSL core and usually not used in app level code.
 * `SplitNode` represents a property access operation which means it is
 * used to implement any `.xyzw`, `.rgba` and `stpq` usage on node objects.
 * For example:
 * ```js
 * const redValue = color.r;
 * ```
 *
 * @augments Node
 */
class SplitNode extends Node {

	static get type() {

		return 'SplitNode';

	}

	/**
	 * Constructs a new split node.
	 *
	 * @param {Node} node - The node that should be accessed.
	 * @param {string} [components='x'] - The components that should be accessed.
	 */
	constructor( node, components = 'x' ) {

		super();

		/**
		 * The node that should be accessed.
		 *
		 * @type {Node}
		 */
		this.node = node;

		/**
		 * The components that should be accessed.
		 *
		 * @type {string}
		 */
		this.components = components;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isSplitNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Returns the vector length which is computed based on the requested components.
	 *
	 * @return {number} The vector length.
	 */
	getVectorLength() {

		let vectorLength = this.components.length;

		for ( const c of this.components ) {

			vectorLength = Math.max( vectorComponents.indexOf( c ) + 1, vectorLength );

		}

		return vectorLength;

	}

	/**
	 * Returns the component type of the node's type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The component type.
	 */
	getComponentType( builder ) {

		return builder.getComponentType( this.node.getNodeType( builder ) );

	}

	/**
	 * This method is overwritten since the node type is inferred from requested components.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		return builder.getTypeFromLength( this.components.length, this.getComponentType( builder ) );

	}

	/**
	 * Returns the scope of the node.
	 *
	 * @return {Node} The scope of the node.
	 */
	getScope() {

		return this.node.getScope();

	}

	generate( builder, output ) {

		const node = this.node;
		const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) );

		let snippet = null;

		if ( nodeTypeLength > 1 ) {

			let type = null;

			const componentsLength = this.getVectorLength();

			if ( componentsLength >= nodeTypeLength ) {

				// needed expand the input node

				type = builder.getTypeFromLength( this.getVectorLength(), this.getComponentType( builder ) );

			}

			const nodeSnippet = node.build( builder, type );

			if ( this.components.length === nodeTypeLength && this.components === _stringVectorComponents.slice( 0, this.components.length ) ) {

				// unnecessary swizzle

				snippet = builder.format( nodeSnippet, type, output );

			} else {

				snippet = builder.format( `${nodeSnippet}.${this.components}`, this.getNodeType( builder ), output );

			}

		} else {

			// ignore .components if .node returns float/integer

			snippet = node.build( builder, output );

		}

		return snippet;

	}

	serialize( data ) {

		super.serialize( data );

		data.components = this.components;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.components = data.components;

	}

}

/**
 * This module is part of the TSL core and usually not used in app level code.
 * `SetNode` represents a set operation which means it is used to implement any
 * `setXYZW()`, `setRGBA()` and `setSTPQ()` method invocations on node objects.
 * For example:
 * ```js
 * materialLine.colorNode = color( 0, 0, 0 ).setR( float( 1 ) );
 * ```
 *
 * @augments Node
 */
class SetNode extends Node {

	static get type() {

		return 'SetNode';

	}

	/**
	 * Constructs a new set node.
	 *
	 * @param {Node} sourceNode - The node that should be updated.
	 * @param {string} components - The components that should be updated.
	 * @param {Node} targetNode - The value node.
	 */
	constructor( sourceNode, components, targetNode ) {

		super();

		/**
		 * The node that should be updated.
		 *
		 * @type {Node}
		 */
		this.sourceNode = sourceNode;

		/**
		 * The components that should be updated.
		 *
		 * @type {string}
		 */
		this.components = components;

		/**
		 * The value node.
		 *
		 * @type {Node}
		 */
		this.targetNode = targetNode;

	}

	/**
	 * This method is overwritten since the node type is inferred from {@link SetNode#sourceNode}.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		return this.sourceNode.getNodeType( builder );

	}

	generate( builder ) {

		const { sourceNode, components, targetNode } = this;

		const sourceType = this.getNodeType( builder );

		const componentType = builder.getComponentType( targetNode.getNodeType( builder ) );
		const targetType = builder.getTypeFromLength( components.length, componentType );

		const targetSnippet = targetNode.build( builder, targetType );
		const sourceSnippet = sourceNode.build( builder, sourceType );

		const length = builder.getTypeLength( sourceType );
		const snippetValues = [];

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

			const component = vectorComponents[ i ];

			if ( component === components[ 0 ] ) {

				snippetValues.push( targetSnippet );

				i += components.length - 1;

			} else {

				snippetValues.push( sourceSnippet + '.' + component );

			}

		}

		return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`;

	}

}

/**
 * This module is part of the TSL core and usually not used in app level code.
 * It represents a flip operation during the shader generation process
 * meaning it flips normalized values with the following formula:
 * ```
 * x = 1 - x;
 * ```
 * `FlipNode` is internally used to implement any `flipXYZW()`, `flipRGBA()` and
 * `flipSTPQ()` method invocations on node objects. For example:
 * ```js
 * uvNode = uvNode.flipY();
 * ```
 *
 * @augments Node
 */
class FlipNode extends Node {

	static get type() {

		return 'FlipNode';

	}

	/**
	 * Constructs a new flip node.
	 *
	 * @param {Node} sourceNode - The node which component(s) should be flipped.
	 * @param {string} components - The components that should be flipped e.g. `'x'` or `'xy'`.
	 */
	constructor( sourceNode, components ) {

		super();

		/**
		 * The node which component(s) should be flipped.
		 *
		 * @type {Node}
		 */
		this.sourceNode = sourceNode;

		/**
		 * The components that should be flipped e.g. `'x'` or `'xy'`.
		 *
		 * @type {string}
		 */
		this.components = components;

	}

	/**
	 * This method is overwritten since the node type is inferred from the source node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		return this.sourceNode.getNodeType( builder );

	}

	generate( builder ) {

		const { components, sourceNode } = this;

		const sourceType = this.getNodeType( builder );
		const sourceSnippet = sourceNode.build( builder );

		const sourceCache = builder.getVarFromNode( this );
		const sourceProperty = builder.getPropertyName( sourceCache );

		builder.addLineFlowCode( sourceProperty + ' = ' + sourceSnippet, this );

		const length = builder.getTypeLength( sourceType );
		const snippetValues = [];

		let componentIndex = 0;

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

			const component = vectorComponents[ i ];

			if ( component === components[ componentIndex ] ) {

				snippetValues.push( '1.0 - ' + ( sourceProperty + '.' + component ) );

				componentIndex ++;

			} else {

				snippetValues.push( sourceProperty + '.' + component );

			}

		}

		return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`;

	}

}

/**
 * Base class for representing data input nodes.
 *
 * @augments Node
 */
class InputNode extends Node {

	static get type() {

		return 'InputNode';

	}

	/**
	 * Constructs a new input node.
	 *
	 * @param {any} value - The value of this node. This can be any JS primitive, functions, array buffers or even three.js objects (vector, matrices, colors).
	 * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value.
	 */
	constructor( value, nodeType = null ) {

		super( nodeType );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isInputNode = true;

		/**
		 * The value of this node. This can be any JS primitive, functions, array buffers or even three.js objects (vector, matrices, colors).
		 *
		 * @type {any}
		 */
		this.value = value;

		/**
		 * The precision of the value in the shader.
		 *
		 * @type {?('low'|'medium'|'high')}
		 * @default null
		 */
		this.precision = null;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generateNodeType( /*builder*/ ) {

		if ( this.nodeType === null ) {

			return getValueType( this.value );

		}

		return this.nodeType;

	}

	/**
	 * Returns the input type of the node which is by default the node type. Derived modules
	 * might overwrite this method and use a fixed type or compute one analytically.
	 *
	 * A typical example for different input and node types are textures. The input type of a
	 * normal RGBA texture is `texture` whereas its node type is `vec4`.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( builder ) {

		return this.getNodeType( builder );

	}

	/**
	 * Sets the precision to the given value. The method can be
	 * overwritten in derived classes if the final precision must be computed
	 * analytically.
	 *
	 * @param {('low'|'medium'|'high')} precision - The precision of the input value in the shader.
	 * @return {InputNode} A reference to this node.
	 */
	setPrecision( precision ) {

		this.precision = precision;

		return this;

	}

	serialize( data ) {

		super.serialize( data );

		data.value = this.value;

		if ( this.value && this.value.toArray ) data.value = this.value.toArray();

		data.valueType = getValueType( this.value );
		data.nodeType = this.nodeType;

		if ( data.valueType === 'ArrayBuffer' ) data.value = arrayBufferToBase64( data.value );

		data.precision = this.precision;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.nodeType = data.nodeType;
		this.value = Array.isArray( data.value ) ? getValueFromType( data.valueType, ...data.value ) : data.value;

		this.precision = data.precision || null;

		if ( this.value && this.value.fromArray ) this.value = this.value.fromArray( data.value );

	}

	generate( /*builder, output*/ ) {

		warn( 'Abstract function.' );

	}

}

const _regNum = /float|u?int/;

/**
 * Class for representing a constant value in the shader.
 *
 * @augments InputNode
 */
class ConstNode extends InputNode {

	static get type() {

		return 'ConstNode';

	}

	/**
	 * Constructs a new input node.
	 *
	 * @param {any} value - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color).
	 * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value.
	 */
	constructor( value, nodeType = null ) {

		super( value, nodeType );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isConstNode = true;

		/**
		 * Whether this constant is an implicit number whose type can adapt to other operands.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.isWeak = false;

	}

	/**
	 * Generates the shader string of the value with the current node builder.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The generated value as a shader string.
	 */
	generateConst( builder ) {

		return builder.generateConst( this.getNodeType( builder ), this.value );

	}

	generate( builder, output ) {

		const type = this.getNodeType( builder );

		if ( _regNum.test( type ) && _regNum.test( output ) ) {

			let value = this.value;

			// Preserve the declared integer value before converting to the output type.
			if ( type === 'int' ) value = Math.trunc( value );
			else if ( type === 'uint' ) value = value >= 0 ? Math.trunc( value ) : 0;

			return builder.generateConst( output, value );

		}

		return builder.format( this.generateConst( builder ), type, output );

	}

	serialize( data ) {

		super.serialize( data );

		data.isWeak = this.isWeak;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.isWeak = data.isWeak === true;

	}

}

/**
 * Base class for representing member access on an object-like
 * node data structures.
 *
 * @augments Node
 */
class MemberNode extends Node {

	static get type() {

		return 'MemberNode';

	}

	/**
	 * Constructs a member node.
	 *
	 * @param {Node} structNode - The struct node.
	 * @param {string} property - The property name.
	 */
	constructor( structNode, property ) {

		super();

		/**
		 * The struct node.
		 *
		 * @type {Node}
		 */
		this.structNode = structNode;

		/**
		 * The property name.
		 *
		 * @type {Node}
		 */
		this.property = property;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isMemberNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Returns the scope of the struct so assignments to members
	 * mark the underlying value as mutable.
	 *
	 * @return {Node} The scope of the node.
	 */
	getScope() {

		return this.structNode.getScope();

	}

	hasMember( builder ) {

		if ( this.structNode.isMemberNode ) {

			if ( this.structNode.hasMember( builder ) === false ) {

				return false;

			}

		}

		return this.structNode.getMemberType( builder, this.property ) !== 'void';

	}

	generateNodeType( builder ) {

		if ( this.hasMember( builder ) === false ) {

			// default type if member does not exist

			return 'float';

		}

		return this.structNode.getMemberType( builder, this.property );

	}

	getMemberType( builder, name ) {

		if ( this.hasMember( builder ) === false ) {

			// default type if member does not exist

			return 'float';

		}

		const type = this.getNodeType( builder );
		const struct = builder.getStructTypeNode( type );

		return struct.getMemberType( builder, name );

	}

	generate( builder ) {

		if ( this.hasMember( builder ) === false ) {

			warn( `TSL: Member "${ this.property }" does not exist in struct.`, this.stackTrace );

			const type = this.getNodeType( builder );

			return builder.generateConst( type );

		}

		const propertyName = this.structNode.build( builder );

		return propertyName + '.' + this.property;

	}

}

let currentStack = null;

const _secureNodeBuilders = new WeakMap();

const NodeElements = new Map();

// Extend Node Class for TSL using prototype

function addMethodChaining( name, nodeElement ) {

	// No require StackTrace because this is internal API

	if ( NodeElements.has( name ) ) {

		warn( `TSL: Redefinition of method chaining '${ name }'.` );
		return;

	}

	if ( typeof nodeElement !== 'function' ) throw new Error( `THREE.TSL: Node element ${ name } is not a function` );

	NodeElements.set( name, nodeElement );

	if ( name !== 'assign' ) {

		// Changing Node prototype to add method chaining

		Node.prototype[ name ] = function ( ...params ) {

			return this.isStackNode ? this.addToStack( nodeElement( ...params ) ) : nodeElement( this, ...params );

		};

		// Adding assign method chaining

		Node.prototype[ name + 'Assign' ] = function ( ...params ) {

			return this.isStackNode ? this.assign( params[ 0 ], nodeElement( ...params ) ) : this.assign( nodeElement( this, ...params ) );

		};

	}

}

const parseSwizzle = ( props ) => props.replace( /r|s/g, 'x' ).replace( /g|t/g, 'y' ).replace( /b|p/g, 'z' ).replace( /a|q/g, 'w' );
const parseSwizzleAndSort = ( props ) => parseSwizzle( props ).split( '' ).sort().join( '' );

Node.prototype.assign = function ( ...params ) {

	if ( this.isStackNode !== true ) {

		if ( currentStack !== null ) {

			currentStack.assign( this, ...params );

		} else {

			error( 'TSL: No stack defined for assign operation. Make sure the assign is inside a Fn().', new StackTrace() );

		}

		return this;

	} else {

		const nodeElement = NodeElements.get( 'assign' );

		return this.addToStack( nodeElement( ...params ) );

	}

};

Node.prototype.get = function ( value ) {

	return new MemberNode( this, value );

};

// Cache prototype for TSL

const proto = {};

// Cache of the swizzle and element nodes, a proxy of a node has its own cache

const _swizzleCache = new WeakMap();

function getSwizzleCache( node ) {

	let cache = _swizzleCache.get( node );

	if ( cache === undefined ) {

		cache = {};

		_swizzleCache.set( node, cache );

	}

	return cache;

}

// Set swizzle properties for xyzw, rgba, and stpq.

function setProtoSwizzle( property, altA, altB ) {

	// swizzle properties

	proto[ property ] = proto[ altA ] = proto[ altB ] = {

		get() {

			const cache = getSwizzleCache( this );

			//

			let split = cache[ property ];

			if ( split === undefined ) {

				split = new SplitNode( this, property );

				cache[ property ] = split;

			}

			return split;

		},

		set( value ) {

			this[ property ].assign( nodeObject( value ) );

		}

	};

	// set properties ( swizzle ) and sort to xyzw sequence

	const propUpper = property.toUpperCase();
	const altAUpper = altA.toUpperCase();
	const altBUpper = altB.toUpperCase();

	// Set methods for swizzle properties

	Node.prototype[ 'set' + propUpper ] = Node.prototype[ 'set' + altAUpper ] = Node.prototype[ 'set' + altBUpper ] = function ( value ) {

		const swizzle = parseSwizzleAndSort( property );

		return new SetNode( this, swizzle, nodeObject( value ) );

	};

	// Set methods for flip properties

	Node.prototype[ 'flip' + propUpper ] = Node.prototype[ 'flip' + altAUpper ] = Node.prototype[ 'flip' + altBUpper ] = function () {

		const swizzle = parseSwizzleAndSort( property );

		return new FlipNode( this, swizzle );

	};

}

const swizzleA = [ 'x', 'y', 'z', 'w' ];
const swizzleB = [ 'r', 'g', 'b', 'a' ];
const swizzleC = [ 's', 't', 'p', 'q' ];

for ( let a = 0; a < 4; a ++ ) {

	let prop = swizzleA[ a ];
	let altA = swizzleB[ a ];
	let altB = swizzleC[ a ];

	setProtoSwizzle( prop, altA, altB );

	for ( let b = 0; b < 4; b ++ ) {

		prop = swizzleA[ a ] + swizzleA[ b ];
		altA = swizzleB[ a ] + swizzleB[ b ];
		altB = swizzleC[ a ] + swizzleC[ b ];

		setProtoSwizzle( prop, altA, altB );

		for ( let c = 0; c < 4; c ++ ) {

			prop = swizzleA[ a ] + swizzleA[ b ] + swizzleA[ c ];
			altA = swizzleB[ a ] + swizzleB[ b ] + swizzleB[ c ];
			altB = swizzleC[ a ] + swizzleC[ b ] + swizzleC[ c ];

			setProtoSwizzle( prop, altA, altB );

			for ( let d = 0; d < 4; d ++ ) {

				prop = swizzleA[ a ] + swizzleA[ b ] + swizzleA[ c ] + swizzleA[ d ];
				altA = swizzleB[ a ] + swizzleB[ b ] + swizzleB[ c ] + swizzleB[ d ];
				altB = swizzleC[ a ] + swizzleC[ b ] + swizzleC[ c ] + swizzleC[ d ];

				setProtoSwizzle( prop, altA, altB );

			}

		}

	}

}

// Set/get static properties for array elements (0-31).

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

	proto[ i ] = {

		get() {

			const cache = getSwizzleCache( this );

			//

			let element = cache[ i ];

			if ( element === undefined ) {

				element = new ArrayElementNode( this, new ConstNode( i, 'uint' ) );

				cache[ i ] = element;

			}

			return element;

		},

		set( value ) {

			this[ i ].assign( nodeObject( value ) );

		}

	};

}

/*
// Set properties for width, height, and depth.

function setProtoProperty( property, target ) {

	proto[ property ] = {

		get() {

			const cache = getSwizzleCache( this );

			//

			let split = cache[ target ];

			if ( split === undefined ) {

				split = new SplitNode( this, target );

				cache[ target ] = split;

			}

			return split;

		},

		set( value ) {

			this[ target ].assign( nodeObject( value ) );

		}

	};

}

setProtoProperty( 'width', 'x' );
setProtoProperty( 'height', 'y' );
setProtoProperty( 'depth', 'z' );
*/

Object.defineProperties( Node.prototype, proto );

// --- FINISH ---

const ShaderNodeObject = function ( obj, altType = null ) {

	const type = getValueType( obj );

	if ( type === 'node' ) {

		return obj;

	} else if ( ( altType === null && ( type === 'float' || type === 'boolean' ) ) || ( type && type !== 'shader' && type !== 'string' ) ) {

		return getConstNode( obj, altType );

	} else if ( type === 'shader' ) {

		return obj.isFn ? obj : Fn( obj );

	}

	return obj;

};

const ShaderNodeObjects = function ( objects, altType = null ) {

	for ( const name in objects ) {

		objects[ name ] = nodeObject( objects[ name ], altType );

	}

	return objects;

};

const ShaderNodeArray = function ( array, altType = null ) {

	const len = array.length;

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

		array[ i ] = nodeObject( array[ i ], altType );

	}

	return array;

};

const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null, settings = null ) {

	function assignNode( node ) {

		if ( settings !== null ) {

			Object.assign( node, settings );

			if ( settings.assignable === true ) {

				node = node.toAssignable();

			}

		}

		return node;


	}

	let fn, name = scope, minParams, maxParams;

	function verifyParamsLimit( params ) {

		let tslName;

		if ( name ) tslName = /[a-z]/i.test( name ) ? name + '()' : name;
		else tslName = NodeClass.type;

		if ( minParams !== undefined && params.length < minParams ) {

			error( `TSL: "${ tslName }" parameter length is less than minimum required.`, new StackTrace() );

			return params.concat( new Array( minParams - params.length ).fill( 0 ) );

		} else if ( maxParams !== undefined && params.length > maxParams ) {

			error( `TSL: "${ tslName }" parameter length exceeds limit.`, new StackTrace() );

			return params.slice( 0, maxParams );

		}

		return params;

	}

	if ( scope === null ) {

		fn = ( ...params ) => {

			return assignNode( new NodeClass( ...nodeArray( verifyParamsLimit( params ) ) ) );

		};

	} else if ( factor !== null ) {

		factor = nodeObject( factor );

		fn = ( ...params ) => {

			return assignNode( new NodeClass( scope, ...nodeArray( verifyParamsLimit( params ) ), factor ) );

		};

	} else {

		fn = ( ...params ) => {

			return assignNode( new NodeClass( scope, ...nodeArray( verifyParamsLimit( params ) ) ) );

		};

	}

	fn.setParameterLength = ( ...params ) => {

		if ( params.length === 1 ) minParams = maxParams = params[ 0 ];
		else if ( params.length === 2 ) [ minParams, maxParams ] = params;

		return fn;

	};

	fn.setName = ( value ) => {

		name = value;

		return fn;

	};

	return fn;

};

const ShaderNodeImmutable = function ( NodeClass, ...params ) {

	return new NodeClass( ...nodeArray( params ) );

};

class ShaderCallNodeInternal extends Node {

	constructor( shaderNode, rawInputs ) {

		super();

		this.shaderNode = shaderNode;
		this.rawInputs = rawInputs;

		this.isShaderCallNodeInternal = true;

	}

	isCacheable( builder ) {

		// A call is an expression unless its body has statements.
		return this.getOutputNode( builder ).nodes.length === 0;

	}

	generateNodeType( builder ) {

		return this.shaderNode.nodeType || this.getOutputNode( builder ).getNodeType( builder );

	}

	getElementType( builder ) {

		return this.getOutputNode( builder ).getElementType( builder );

	}

	getMemberType( builder, name ) {

		return this.getOutputNode( builder ).getMemberType( builder, name );

	}

	call( builder ) {

		const { shaderNode, rawInputs } = this;

		const properties = builder.getNodeProperties( shaderNode );

		const subBuild = builder.getClosestSubBuild( shaderNode.subBuilds ) || '';
		const subBuildProperty = subBuild || 'default';

		if ( properties[ subBuildProperty ] ) {

			return properties[ subBuildProperty ];

		}

		//

		const previousSubBuildFn = builder.subBuildFn;
		const previousFnCall = builder.fnCall;

		builder.subBuildFn = subBuild;
		builder.fnCall = this;

		let result = null;

		// A layout call needs every input. Missing inputs are completed with the default values
		// of the function parameters; if they can't be resolved, the function is expanded inline.

		const layoutInputs = shaderNode.layout ? builder._getLayoutInputs( shaderNode, rawInputs ) : null;

		if ( layoutInputs !== null ) {

			// build inputs first, use layout inputs to ensure that no extra parameters are built

			const layoutParameters = shaderNode.layout.inputs;

			if ( isArrayAsParameter( layoutInputs ) ) {

				const rawArrayParameters = layoutInputs;

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

					const rawParameter = rawArrayParameters[ i ];

					if ( rawParameter && rawParameter.isNode ) {

						rawParameter.build( builder );

					}

				}

			} else {

				const rawObjectParameters = layoutInputs[ 0 ];

				for ( const param of layoutParameters ) {

					const rawParameter = rawObjectParameters[ param.name ];

					if ( rawParameter && rawParameter.isNode ) {

						rawParameter.build( builder );

					}

				}

			}

			const functionNode = builder.buildFunctionNode( shaderNode );

			builder.addInclude( functionNode );

			//

			const inputs = getLayoutParameters( layoutInputs );

			result = functionNode.call( inputs );

		} else {

			let secureNodeBuilder = _secureNodeBuilders.get( builder );

			if ( secureNodeBuilder === undefined ) {

				secureNodeBuilder = new Proxy( builder, {

					get: ( target, property, receiver ) => {

						let value;

						if ( Symbol.iterator === property ) {

							value = function* () {

								yield undefined;

							};

						} else {

							value = Reflect.get( target, property, receiver );

						}

						return value;

					}

				} );

				_secureNodeBuilders.set( builder, secureNodeBuilder );

			}

			//

			const inputs = rawInputs ? getProxyParameters( rawInputs ) : null;

			const hasParameters = Array.isArray( rawInputs ) ? rawInputs.length > 0 : rawInputs !== null;

			const jsFunc = shaderNode.jsFunc;
			const outputNode = hasParameters || jsFunc.length > 1 ? jsFunc( inputs, secureNodeBuilder ) : jsFunc( secureNodeBuilder );

			result = nodeObject( outputNode );

		}

		builder.subBuildFn = previousSubBuildFn;
		builder.fnCall = previousFnCall;

		if ( shaderNode.once ) {

			properties[ subBuildProperty ] = result;

		}

		return result;

	}

	setupOutput( builder ) {

		builder.addStack();

		builder.stack.outputNode = this.call( builder );

		return builder.removeStack();

	}

	getOutputNode( builder ) {

		const properties = builder.getNodeProperties( this );
		const subBuildOutput = builder.getSubBuildOutput( this );

		properties[ subBuildOutput ] = properties[ subBuildOutput ] || this.setupOutput( builder );
		properties[ subBuildOutput ].subBuild = builder.getClosestSubBuild( this );

		return properties[ subBuildOutput ];

	}

	generate( builder, output ) {

		return this.getOutputNode( builder ).build( builder, output ) || '';

	}

	build( builder, output = null ) {

		// The call does not use Node.build(), so it handles the inspector node, shared and overridden nodes and assignable state here.
		this._setupInspector( builder );

		const refNode = this.getShared( builder );

		if ( this !== refNode ) {

			return refNode.build( builder, output );

		}

		let result = null;

		const buildStage = builder.getBuildStage();
		const properties = builder.getNodeProperties( this );

		const nodeData = this.assignable === true ? builder.getDataFromNode( this ) : null;

		if ( nodeData !== null ) this._updateAssignable( builder, nodeData );

		const subBuildOutput = builder.getSubBuildOutput( this );
		const outputNode = this.getOutputNode( builder );

		const previousFnCall = builder.fnCall;

		builder.fnCall = this;

		if ( buildStage === 'setup' ) {

			const subBuildInitialized = builder.getSubBuildProperty( 'initialized', this );

			if ( properties[ subBuildInitialized ] !== true ) {

				properties[ subBuildInitialized ] = true;

				properties[ subBuildOutput ] = this.getOutputNode( builder );
				properties[ subBuildOutput ].build( builder );

				// If the shaderNode has subBuilds, add them to the chaining nodes
				// so they can be built later in the build process.

				if ( this.shaderNode.subBuilds ) {

					for ( const node of builder.chaining ) {

						const nodeData = builder.getDataFromNode( node, 'any' );
						nodeData.subBuilds = nodeData.subBuilds || new Set();

						for ( const subBuild of this.shaderNode.subBuilds ) {

							nodeData.subBuilds.add( subBuild );

						}

						//builder.getDataFromNode( node ).subBuilds = nodeData.subBuilds;

					}

				}

			}

			result = properties[ subBuildOutput ];

		} else if ( buildStage === 'analyze' ) {

			// A variable is evaluated once, so its value is analyzed only once.
			if ( nodeData === null || this._isVariable( builder, nodeData ) === false || builder.increaseUsage( this ) === 1 ) {

				outputNode.build( builder, output );

			}

		} else if ( buildStage === 'generate' ) {

			if ( nodeData !== null && this._isVariable( builder, nodeData ) ) {

				this._checkFlowBlock( builder, nodeData );

				const isCached = nodeData.propertyName !== undefined;
				const flowCodeLength = builder.flow.code.length;

				result = this._buildVariable( builder, nodeData, output );

				this._setFlowBlock( builder, nodeData, isCached, flowCodeLength );

			} else {

				result = outputNode.build( builder, output ) || '';

			}

		}

		builder.fnCall = previousFnCall;

		return result;

	}

}

function getLayoutParameters( params ) {

	let output;

	nodeObjects( params );

	if ( isArrayAsParameter( params ) ) {

		output = [ ...params ];

	} else {

		output = params[ 0 ];

	}

	return output;

}

function getProxyParameters( params ) {

	let index = 0;

	nodeObjects( params );

	return new Proxy( params, {

		get: ( target, property, receiver ) => {

			let value;

			if ( property === 'length' ) {

				value = params.length;

				return value;

			}

			if ( Symbol.iterator === property ) {

				value = function* () {

					for ( const inputNode of params ) {

						yield inputNode;

					}

				};

			} else {

				if ( params.length > 0 ) {

					if ( Object.getPrototypeOf( params[ 0 ] ) === Object.prototype ) {

						const objectTarget = params[ 0 ];

						if ( objectTarget[ property ] === undefined ) {

							value = objectTarget[ index ++ ];

						} else {

							value = Reflect.get( objectTarget, property, receiver );

						}

					} else if ( params[ 0 ] instanceof Node ) {

						if ( params[ property ] === undefined ) {

							value = params[ index ++ ];

						} else {

							value = Reflect.get( params, property, receiver );

						}

					}

				} else {

					value = Reflect.get( target, property, receiver );

				}

				value = nodeObject( value );

			}

			return value;

		}

	} );

}

class ShaderNodeInternal extends Node {

	constructor( jsFunc, nodeType ) {

		super( nodeType );

		this.jsFunc = jsFunc;
		this.layout = null;

		this.global = true;

		this.once = false;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	setLayout( layout ) {

		this.layout = layout;

		return this;

	}

	getLayout() {

		return this.layout;

	}

	call( rawInputs = null ) {

		return new ShaderCallNodeInternal( this, rawInputs );

	}

	setup() {

		return this.call();

	}

}

const bools = [ false, true ];
const uints = [ 0, 1, 2, 3 ];
const ints = [ -1, -2 ];
const floats = [ 0.5, 1.5, 1 / 3, 1e-6, 1e6, Math.PI, Math.PI * 2, 1 / Math.PI, 2 / Math.PI, 1 / ( Math.PI * 2 ), Math.PI / 2 ];

const boolsCacheMap = new Map();
for ( const bool of bools ) boolsCacheMap.set( bool, new ConstNode( bool ) );

const uintsCacheMap = new Map();
for ( const uint of uints ) uintsCacheMap.set( uint, new ConstNode( uint, 'uint' ) );

const intsCacheMap = new Map();
for ( const value of uintsCacheMap.keys() ) intsCacheMap.set( value, new ConstNode( value, 'int' ) );
for ( const int of ints ) intsCacheMap.set( int, new ConstNode( int, 'int' ) );

const floatsCacheMap = new Map();
for ( const value of intsCacheMap.keys() ) floatsCacheMap.set( value, new ConstNode( value ) );
for ( const float of floats ) floatsCacheMap.set( float, new ConstNode( float ) );
for ( const float of floats ) floatsCacheMap.set( - float, new ConstNode( - float ) );

const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap };

const constNodesCacheMap = new Map( boolsCacheMap );

const getConstNode = ( value, type ) => {

	if ( constNodesCacheMap.has( value ) ) {

		return constNodesCacheMap.get( value );

	} else if ( value.isNode === true ) {

		return value;

	} else {

		const node = new ConstNode( value, type );

		// Implicit numbers are weak and can adapt to the type of other operands.
		node.isWeak = ! type && typeof value === 'number';

		return node;

	}

};

for ( const value of floatsCacheMap.keys() ) constNodesCacheMap.set( value, getConstNode( value ) );

const ConvertType = function ( type, cacheMap = null ) {

	return ( ...params ) => {

		for ( const param of params ) {

			if ( param === undefined ) {

				error( `TSL: Invalid parameter for the type "${ type }".`, new StackTrace() );

				return new ConstNode( 0, type );

			}

		}

		if ( params.length === 0 || ( ! [ 'bool', 'float', 'int', 'uint' ].includes( type ) && params.every( param => {

			const paramType = typeof param;

			return paramType !== 'object' && paramType !== 'function';

		} ) ) ) {

			params = [ getValueFromType( type, ...params ) ];

		}

		if ( params.length === 1 && cacheMap !== null && cacheMap.has( params[ 0 ] ) ) {

			const cached = cacheMap.get( params[ 0 ] );

			return new ConstNode( cached.value, cached.nodeType ).toAssignable();

		}

		if ( params.length === 1 ) {

			const param = params[ 0 ];
			const node = getConstNode( param, type );

			if ( node.nodeType === type ) {

				// A node given by the user or a shared constant must not become the variable itself.
				if ( node === param ) return new ConvertNode( node, type ).toAssignable();
				if ( constNodesCacheMap.get( param ) === node ) return new ConstNode( node.value, node.nodeType ).toAssignable();

				return node.toAssignable();

			}

			return new ConvertNode( node, type ).toAssignable();

		}

		const nodes = params.map( param => getConstNode( param ) );
		return new JoinNode( nodes, type ).toAssignable();

	};

};

// exports

function defined( value ) {

	if ( value && value.isNode ) {

		value.traverse( ( node ) => {

			if ( node.isConstNode ) {

				value = node.value;

			}

		} );

	}

	return Boolean( value );

}

// utils

const getConstNodeType = ( value ) => ( value !== undefined && value !== null ) ? ( value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ) ) : null;

// shader node base

function ShaderNode( jsFunc, nodeType ) {

	return new ShaderNodeInternal( jsFunc, nodeType );

}

const nodeObject = ( val, altType = null ) => /* new */ ShaderNodeObject( val, altType );
const nodeObjectAssignable = ( val, altType = null ) => {

	let node = nodeObject( val, altType );

	// Shared constants need their own assignable node.
	if ( node.isConstNode === true && constNodesCacheMap.get( node.value ) === node ) {

		const copy = new ConstNode( node.value, node.nodeType );
		copy.isWeak = node.isWeak;
		node = copy;

	}

	return node.toAssignable();

};

const nodeObjects = ( val, altType = null ) => new ShaderNodeObjects( val, altType );
const nodeArray = ( val, altType = null ) => new ShaderNodeArray( val, altType );
const nodeProxy = ( NodeClass, scope = null, factor = null, settings = null ) => new ShaderNodeProxy( NodeClass, scope, factor, settings );
const nodeImmutable = ( NodeClass, ...params ) => new ShaderNodeImmutable( NodeClass, ...params );
const nodeProxyAssignable = ( NodeClass, scope = null, factor = null, settings = {} ) => new ShaderNodeProxy( NodeClass, scope, factor, { ...settings, assignable: true } );

const nodeProxyConstructor = ( constructorFunction, nodeInstance ) => {

	return new Proxy( constructorFunction, {

		get( target, prop, receiver ) {

			return Reflect.get( nodeInstance, prop, receiver );

		},

		set( target, prop, value ) {

			return Reflect.set( nodeInstance, prop, value );

		}

	} );

};

let fnId = 0;

class FnNode extends Node {

	constructor( jsFunc, layout = null ) {

		super();

		let nodeType = null;

		if ( layout !== null ) {

			if ( typeof layout === 'object' ) {

				nodeType = layout.return;

			} else {

				if ( typeof layout === 'string' ) {

					nodeType = layout;

				} else {

					error( 'TSL: Invalid layout type.', new StackTrace() );

				}

				layout = null;

			}

		}

		this.shaderNode = new ShaderNode( jsFunc, nodeType );

		if ( layout !== null ) {

			this.setLayout( layout );

		}

		this.isFn = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	setLayout( layout ) {

		const nodeType = this.shaderNode.nodeType;

		if ( typeof layout.inputs !== 'object' ) {

			const fullLayout = {
				name: 'fn' + fnId ++,
				type: nodeType,
				inputs: []
			};

			for ( const name in layout ) {

				if ( name === 'return' ) continue;

				fullLayout.inputs.push( {
					name: name,
					type: layout[ name ]
				} );

			}

			layout = fullLayout;

		}

		this.shaderNode.setLayout( layout );

		return this;

	}

	generateNodeType( builder ) {

		return this.shaderNode.getNodeType( builder ) || 'float';

	}

	call( ...params ) {

		const fnCall = this.shaderNode.call( params );

		// A call without a return value is a statement.
		if ( this.shaderNode.nodeType === 'void' ) return fnCall.toStack();

		return fnCall.toAssignable();

	}

	once( subBuilds = null ) {

		this.shaderNode.once = true;
		this.shaderNode.subBuilds = subBuilds;

		return this;

	}

	generate( builder ) {

		const type = this.getNodeType( builder );

		error( 'TSL: "Fn()" was declared but not invoked. Try calling it like "Fn()( ...params )".', this.stackTrace );

		return builder.generateConst( type );

	}

}

function Fn( jsFunc, layout = null ) {

	// Layout functions are cached across shaders of the same backend. Their bodies
	// must depend only on explicit inputs and constants, not uniforms or material context.
	//
	// Parameters can have default values. A call without some inputs passes the defaults as
	// arguments, so values like `uv()` are evaluated at the call site. The defaults are resolved
	// once per function and shared by all calls: a value that must be evaluated per build should
	// be a node like `Fn( ... ).once()().toVar( name )`. If a default depends on another
	// parameter, that call is expanded inline.

	const instance = new FnNode( jsFunc, layout );

	return new Proxy( () => {}, {

		apply( target, thisArg, params ) {

			return instance.call( ...params );

		},

		get( target, prop, receiver ) {

			return Reflect.get( instance, prop, receiver );

		},

		set( target, prop, value, receiver ) {

			return Reflect.set( instance, prop, value, receiver );

		}

	} );

}

//

const setCurrentStack = ( stack ) => {

	currentStack = stack;

};

const getCurrentStack = () => currentStack;

/**
 * Represent a conditional node using if/else statements.
 *
 * ```js
 * If( condition, function )
 * 	.ElseIf( condition, function )
 * 	.Else( function )
 * ```
 * @tsl
 * @function
 * @param {...any} params - The parameters for the conditional node.
 * @returns {StackNode} The conditional node.
 */
const If = ( ...params ) => currentStack.If( ...params );

/**
 * Represent a conditional node using switch/case statements.
 *
 * ```js
 * Switch( value )
 * 	.Case( 1, function )
 * 	.Case( 2, 3, 4, function )
 * 	.Default( function )
 * ```
 * @tsl
 * @function
 * @param {...any} params - The parameters for the conditional node.
 * @returns {StackNode} The conditional node.
 */
const Switch = ( ...params ) => currentStack.Switch( ...params );

/**
 * Add the given node to the current stack.
 *
 * @param {Node} node - The node to add.
 * @returns {Node} The node that was added to the stack.
 */
function Stack( node ) {

	if ( currentStack ) currentStack.addToStack( node );

	return node;

}

addMethodChaining( 'toStack', Stack );

// types

const color = new ConvertType( 'color' );

const float = new ConvertType( 'float', cacheMaps.float );
const int = new ConvertType( 'int', cacheMaps.ints );
const uint = new ConvertType( 'uint', cacheMaps.uint );
const bool = new ConvertType( 'bool', cacheMaps.bool );

const vec2 = new ConvertType( 'vec2' );
const ivec2 = new ConvertType( 'ivec2' );
const uvec2 = new ConvertType( 'uvec2' );
const bvec2 = new ConvertType( 'bvec2' );

const vec3 = new ConvertType( 'vec3' );
const ivec3 = new ConvertType( 'ivec3' );
const uvec3 = new ConvertType( 'uvec3' );
const bvec3 = new ConvertType( 'bvec3' );

const vec4 = new ConvertType( 'vec4' );
const ivec4 = new ConvertType( 'ivec4' );
const uvec4 = new ConvertType( 'uvec4' );
const bvec4 = new ConvertType( 'bvec4' );

const mat2 = new ConvertType( 'mat2' );
const mat3 = new ConvertType( 'mat3' );
const mat4 = new ConvertType( 'mat4' );

addMethodChaining( 'toColor', color );
addMethodChaining( 'toFloat', float );
addMethodChaining( 'toInt', int );
addMethodChaining( 'toUint', uint );
addMethodChaining( 'toBool', bool );
addMethodChaining( 'toVec2', vec2 );
addMethodChaining( 'toIVec2', ivec2 );
addMethodChaining( 'toUVec2', uvec2 );
addMethodChaining( 'toBVec2', bvec2 );
addMethodChaining( 'toVec3', vec3 );
addMethodChaining( 'toIVec3', ivec3 );
addMethodChaining( 'toUVec3', uvec3 );
addMethodChaining( 'toBVec3', bvec3 );
addMethodChaining( 'toVec4', vec4 );
addMethodChaining( 'toIVec4', ivec4 );
addMethodChaining( 'toUVec4', uvec4 );
addMethodChaining( 'toBVec4', bvec4 );
addMethodChaining( 'toMat2', mat2 );
addMethodChaining( 'toMat3', mat3 );
addMethodChaining( 'toMat4', mat4 );

// basic nodes

const element = /*@__PURE__*/ nodeProxy( ArrayElementNode ).setParameterLength( 2 );
const convert = ( node, types ) => new ConvertNode( nodeObject( node ), types );
const split = ( node, channels ) => new SplitNode( nodeObject( node ), channels );

addMethodChaining( 'element', element );
addMethodChaining( 'convert', convert );

/**
 * These node represents an assign operation. Meaning a node is assigned
 * to another node.
 *
 * @augments Node
 */
class AssignNode extends Node {

	static get type() {

		return 'AssignNode';

	}

	/**
	 * Constructs a new assign node.
	 *
	 * @param {Node} targetNode - The target node.
	 * @param {Node} sourceNode - The source type.
	 */
	constructor( targetNode, sourceNode ) {

		super();

		/**
		 * The target node.
		 *
		 * @type {Node}
		 */
		this.targetNode = targetNode;

		/**
		 * The source node.
		 *
		 * @type {Node}
		 */
		this.sourceNode = sourceNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isAssignNode = true;

	}

	/**
	 * Assignments must not be cached in temporary variables.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {boolean} Always `false`.
	 */
	isCacheable( /*builder*/ ) {

		return false;

	}

	generateNodeType( builder, output ) {

		return output !== 'void' ? this.targetNode.getNodeType( builder ) : 'void';

	}

	/**
	 * Whether a split is required when assigning source to target. This can happen when the component length of
	 * target and source data type does not match.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {boolean} Whether a split is required when assigning source to target.
	 */
	needsSplitAssign( builder ) {

		const { targetNode } = this;

		if ( builder.isAvailable( 'swizzleAssign' ) === false && targetNode.isSplitNode && targetNode.components.length > 1 ) {

			const targetLength = builder.getTypeLength( targetNode.node.getNodeType( builder ) );
			const assignDifferentVector = vectorComponents.join( '' ).slice( 0, targetLength ) !== targetNode.components;

			return assignDifferentVector;

		}

		return false;

	}

	setup( builder ) {

		const { targetNode, sourceNode } = this;

		const scope = targetNode.getScope();

		const scopeData = builder.getDataFromNode( scope );
		scopeData.assign = true;

		const properties = builder.getNodeProperties( this );
		properties.sourceNode = sourceNode;
		properties.targetNode = targetNode.context( { assign: true } );

	}

	generate( builder, output ) {

		const { targetNode, sourceNode } = builder.getNodeProperties( this );

		const needsSplitAssign = this.needsSplitAssign( builder );

		const target = targetNode.build( builder );
		const targetType = targetNode.getNodeType( builder );

		const source = sourceNode.build( builder, targetType );
		const sourceType = sourceNode.getNodeType( builder );

		const nodeData = builder.getDataFromNode( this );

		//

		let snippet;

		if ( nodeData.propertyName !== undefined ) {

			if ( output !== 'void' ) {

				snippet = target;

			}

		} else if ( needsSplitAssign ) {

			const sourceVar = builder.getVarFromNode( this, null, targetType );
			const sourceProperty = builder.getPropertyName( sourceVar );

			builder.addLineFlowCode( `${ sourceProperty } = ${ source }`, this );

			const splitNode = targetNode.node;
			const splitTargetNode = splitNode.node.context( { assign: true } );

			const targetRoot = splitTargetNode.build( builder );

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

				const component = splitNode.components[ i ];

				builder.addLineFlowCode( `${ targetRoot }.${ component } = ${ sourceProperty }[ ${ i } ]`, this );

			}

			if ( output !== 'void' ) {

				snippet = target;

			}

		} else {

			snippet = `${ target } = ${ source }`;

			if ( output === 'void' || sourceType === 'void' ) {

				builder.addLineFlowCode( snippet, this );

				if ( output !== 'void' ) {

					snippet = target;

				}

			}

		}

		// The value of an assignment is its target.
		nodeData.propertyName = target;

		return builder.format( snippet, targetType, output );

	}

}

/**
 * TSL function for creating an assign node.
 *
 * @tsl
 * @function
 * @param {Node} targetNode - The target node.
 * @param {Node} sourceNode - The source type.
 * @returns {AssignNode}
 */
const assign = /*@__PURE__*/ nodeProxy( AssignNode ).setParameterLength( 2 );

addMethodChaining( 'assign', assign );

/**
 * ArrayNode represents a collection of nodes, typically created using the {@link array} function.
 * ```js
 * const colors = array( [
 * 	vec3( 1, 0, 0 ),
 * 	vec3( 0, 1, 0 ),
 * 	vec3( 0, 0, 1 )
 * ] );
 *
 * const redColor = tintColors.element( 0 );
 * ```
 *
 * @augments Node
 */
class ArrayNode extends Node {

	static get type() {

		return 'ArrayNode';

	}

	/**
	 * Constructs a new array node.
	 *
	 * @param {?string} nodeType - The data type of the elements.
	 * @param {number} count - Size of the array.
	 * @param {?Array<Node>} [values=null] - Array default values.
	 */
	constructor( nodeType, count, values = null ) {

		super( nodeType );

		/**
		 * Array size.
		 *
		 * @type {number}
		 */
		this.count = count;

		/**
		 * Array default values.
		 *
		 * @type {?Array<Node>}
		 */
		this.values = values;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isArrayNode = true;

	}

	/**
	 * Returns the number of elements in the node array.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {number} The number of elements in the node array.
	 */
	getArrayCount( /*builder*/ ) {

		return this.count;

	}

	/**
	 * Returns the node's type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The type of the node.
	 */
	generateNodeType( builder ) {

		if ( this.nodeType === null ) {

			return this.values[ 0 ].getNodeType( builder );

		}

		return this.nodeType;

	}

	/**
	 * Returns the node's type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The type of the node.
	 */
	getElementType( builder ) {

		return this.getNodeType( builder );

	}

	/**
	 * Returns the type of a member variable.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} name - The name of the member variable.
	 * @return {string} The type of the member variable.
	 */
	getMemberType( builder, name ) {

		if ( this.nodeType === null ) {

			return this.values[ 0 ].getMemberType( builder, name );

		}

		return super.getMemberType( builder, name );

	}

	/**
	 * This method builds the output node and returns the resulting array as a shader string.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The generated shader string.
	 */
	generate( builder ) {

		const type = this.getNodeType( builder );

		return builder.generateArray( type, this.count, this.values );

	}

}

/**
 * TSL function for creating an array node.
 *
 * @tsl
 * @function
 * @param {string|Array<Node>} nodeTypeOrValues - A string representing the element type (e.g., 'vec3')
 * or an array containing the default values (e.g., [ vec3() ]).
 * @param {?number} [count] - Size of the array.
 * @returns {ArrayNode}
 */
const array = ( ...params ) => {

	let node;

	if ( params.length === 1 ) {

		const values = params[ 0 ].map( ( value ) => nodeObject( value ) );

		node = new ArrayNode( null, values.length, values );

	} else {

		const nodeType = params[ 0 ];
		const count = params[ 1 ];

		node = new ArrayNode( nodeType, count );

	}

	return node;

};

addMethodChaining( 'toArray', ( node, count ) => array( Array( count ).fill( node ) ) );

/**
 * This node can be used to group single instances of {@link UniformNode}
 * and manage them as a uniform buffer.
 *
 * In most cases, the predefined nodes `objectGroup`, `renderGroup` and `frameGroup`
 * will be used when defining the {@link UniformNode#groupNode} property.
 *
 * - `objectGroup`: Uniform buffer per object.
 * - `renderGroup`: Shared uniform buffer, updated once per render call.
 * - `frameGroup`: Shared uniform buffer, updated once per frame.
 *
 * @augments Node
 */
class UniformGroupNode extends Node {

	static get type() {

		return 'UniformGroupNode';

	}

	/**
	 * Constructs a new uniform group node.
	 *
	 * @param {string} name - The name of the uniform group node.
	 * @param {boolean} [shared=false] - Whether this uniform group node is shared or not.
	 * @param {number} [order=1] - Influences the internal sorting.
	 * @param {string|null} [updateType=null] - The update type of the uniform group node.
	 */
	constructor( name, shared = false, order = 1, updateType = null ) {

		super( 'string' );

		/**
		 * The name of the uniform group node.
		 *
		 * @type {string}
		 */
		this.name = name;

		/**
		 * Whether this uniform group node is shared or not.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.shared = shared;

		/**
		 * The sort key that determines the order of this group relative to other
		 * uniform groups. Groups with a lower order are placed first. Shared groups
		 * typically use a lower order so they precede regular per-object groups.
		 *
		 * @type {number}
		 * @default 1
		 */
		this.order = order;

		/**
		 * The update type of the uniform group node.
		 *
		 * @type {string|null}
		 * @default null
		 */
		this.updateType = updateType;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isUniformGroup = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Marks the uniform group node as needing an update.
	 * This will trigger the necessary updates in the rendering process.
	 */
	update() {

		this.needsUpdate = true;

	}

	/**
	 * Serializes the uniform group node to a JSON object.
	 *
	 * @param {Object} data - The object to store the serialized data.
	 */
	serialize( data ) {

		super.serialize( data );

		data.name = this.name;
		data.version = this.version;
		data.shared = this.shared;

	}

	/**
	 * Deserializes the uniform group node from a JSON object.
	 *
	 * @param {Object} data - The object containing the serialized data.
	 */
	deserialize( data ) {

		super.deserialize( data );

		this.name = data.name;
		this.version = data.version;
		this.shared = data.shared;

	}

}

/**
 * TSL function for creating a uniform group node with the given name.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the uniform group node.
 * @returns {UniformGroupNode}
 */
const uniformGroup = ( name, order = 1, updateType = null ) => new UniformGroupNode( name, false, order, updateType );

/**
 * TSL function for creating a shared uniform group node with the given name and order.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the uniform group node.
 * @param {number} [order=0] - Influences the internal sorting.
 * @returns {UniformGroupNode}
 */
const sharedUniformGroup = ( name, order = 0, updateType = null ) => new UniformGroupNode( name, true, order, updateType );

/**
 * TSL object that represents a shared uniform group node which is updated once per frame.
 *
 * @tsl
 * @type {UniformGroupNode}
 */
const frameGroup = /*@__PURE__*/ sharedUniformGroup( 'frame', 0, NodeUpdateType.FRAME );

/**
 * TSL object that represents a shared uniform group node which is updated once per render.
 *
 * @tsl
 * @type {UniformGroupNode}
 */
const renderGroup = /*@__PURE__*/ sharedUniformGroup( 'render', 0, NodeUpdateType.RENDER );

/**
 * TSL object that represents a uniform group node which is updated once per object.
 *
 * @tsl
 * @type {UniformGroupNode}
 */
const objectGroup = /*@__PURE__*/ uniformGroup( 'object', 1, NodeUpdateType.OBJECT );

/**
 * Class for representing a uniform.
 *
 * @augments InputNode
 */
class UniformNode extends InputNode {

	static get type() {

		return 'UniformNode';

	}

	/**
	 * Constructs a new uniform node.
	 *
	 * @param {any} value - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color, texture).
	 * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value.
	 */
	constructor( value, nodeType = null ) {

		super( value, nodeType );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isUniformNode = true;

		/**
		 * The name or label of the uniform.
		 *
		 * @type {string}
		 * @default ''
		 */
		this.name = '';

		/**
		 * The uniform group of this uniform. By default, uniforms are
		 * managed per object but they might belong to a shared group
		 * which is updated per frame or render call.
		 *
		 * @type {UniformGroupNode}
		 */
		this.groupNode = objectGroup;

	}

	/**
	 * Sets the {@link UniformNode#name} property.
	 *
	 * @param {string} name - The name of the uniform.
	 * @return {UniformNode} A reference to this node.
	 */
	setName( name ) {

		this.name = name;

		return this;

	}

	/**
	 * Sets the {@link UniformNode#name} property.
	 *
	 * @deprecated
	 * @param {string} name - The name of the uniform.
	 * @return {UniformNode} A reference to this node.
	 */
	label( name ) {

		warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.', new StackTrace() ); // @deprecated r179

		return this.setName( name );

	}

	/**
	 * Sets the {@link UniformNode#groupNode} property.
	 *
	 * @param {UniformGroupNode} group - The uniform group.
	 * @return {UniformNode} A reference to this node.
	 */
	setGroup( group ) {

		this.groupNode = group;

		return this;

	}

	/**
	 * Returns the {@link UniformNode#groupNode}.
	 *
	 * @return {UniformGroupNode} The uniform group.
	 */
	getGroup() {

		return this.groupNode;

	}

	/**
	 * By default, this method returns the result of {@link Node#getHash} but derived
	 * classes might overwrite this method with a different implementation.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The uniform hash.
	 */
	getUniformHash( builder ) {

		return this.getHash( builder );

	}

	/**
	 * Uniform nodes with the same hash share a single uniform. This method returns the node
	 * the shared uniform refers to which is the first node registered for the hash.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {UniformNode} The node the shared uniform refers to.
	 */
	getSharedNode( builder ) {

		const hash = this.getUniformHash( builder );

		let sharedNode = builder.getNodeFromHash( hash );

		if ( sharedNode === undefined ) {

			builder.setHashNode( this, hash );

			sharedNode = this;

		}

		return sharedNode;

	}

	onUpdate( callback, updateType ) {

		callback = callback.bind( this );

		return super.onUpdate( ( frame ) => {

			const value = callback( frame, this );

			if ( value !== undefined ) {

				this.value = value;

			}

	 	}, updateType );

	}

	getInputType( builder ) {

		let type = super.getInputType( builder );

		if ( type === 'bool' ) {

			type = 'uint';

		}

		return type;

	}

	generate( builder, output ) {

		const type = this.getNodeType( builder );

		const sharedNode = this.getSharedNode( builder );
		const sharedNodeType = sharedNode.getInputType( builder );

		const nodeUniform = builder.getUniformFromNode( sharedNode, sharedNodeType, builder.shaderStage, this.name || builder.context.nodeName );
		const uniformName = builder.getPropertyName( nodeUniform );

		if ( builder.context.nodeName !== undefined ) delete builder.context.nodeName;

		//

		let snippet = uniformName;

		if ( type === 'bool' ) {

			// cache to variable

			const nodeData = builder.getDataFromNode( this );

			let propertyName = nodeData.propertyName;

			if ( propertyName === undefined ) {

				const nodeVar = builder.getVarFromNode( this, null, 'bool' );
				propertyName = builder.getPropertyName( nodeVar );

				nodeData.propertyName = propertyName;

				snippet = builder.format( uniformName, sharedNodeType, type );

				builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this );

			}

			snippet = propertyName;

		}

		return builder.format( snippet, type, output );

	}

}

/**
 * TSL function for creating a uniform node.
 *
 * @tsl
 * @function
 * @param {any|string} value - The value of this uniform or your type. Usually a JS primitive or three.js object (vector, matrix, color, texture).
 * @param {string} [type] - The node type. If no explicit type is defined, the node tries to derive the type from its value.
 * @returns {UniformNode}
 */
const uniform = ( value, type ) => {

	const nodeType = getConstNodeType( type || value );

	if ( nodeType === value ) {

		// if the value is a type but no having a value

		value = getValueFromType( nodeType );

	}

	if ( value && value.isNode === true ) {

		let v = value.value;

		value.traverse( n => {

			if ( n.isConstNode === true ) {

				v = n.value;

			}

		} );

		value = v;

	}

	return new UniformNode( value, nodeType );

};

/**
 * This class represents a shader property. It can be used
 * to explicitly define a property and assign a value to it.
 *
 * ```js
 * const threshold = property( 'float', 'threshold' ).assign( THRESHOLD );
 *```
 * `PropertyNode` is used by the engine to predefined common material properties
 * for TSL code.
 *
 * @augments Node
 */
class PropertyNode extends Node {

	static get type() {

		return 'PropertyNode';

	}

	/**
	 * Constructs a new property node.
	 *
	 * @param {string} nodeType - The type of the node.
	 * @param {?string} [name=null] - The name of the property in the shader.
	 * @param {boolean} [varying=false] - Whether this property is a varying or not.
	 * @param {?Node} [placeholderNode=null] - The placeholder node if not assigned.
	 */
	constructor( nodeType, name = null, varying = false, placeholderNode = null ) {

		super( nodeType );

		/**
		 * The name of the property in the shader. If no name is defined,
		 * the node system auto-generates one.
		 *
		 * @type {?string}
		 * @default null
		 */
		this.name = name;

		/**
		 * Whether this property is a varying or not.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.varying = varying;

		/**
		 * The placeholder node of the property if it is not assigned.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.placeholderNode = nodeObject( placeholderNode );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isPropertyNode = true;

		/**
		 * This flag is used for global cache.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	getNodeType( builder ) {

		const nodeType = super.getNodeType( builder );

		if ( nodeType === 'output' ) {

			return builder.getOutputType();

		}

		return nodeType;

	}

	customCacheKey() {

		return hashString( this.type + ':' + ( this.name || '' ) + ':' + ( this.varying ? '1' : '0' ) );

	}

	getHash( builder ) {

		return this.name || super.getHash( builder );

	}

	generate( builder ) {

		let nodeVar;

		if ( this.varying === true ) {

			nodeVar = builder.getVaryingFromNode( this, this.name );
			nodeVar.needsInterpolation = true;

		} else {

			nodeVar = builder.getVarFromNode( this, this.name );

			if ( this.placeholderNode !== null ) {

				if ( builder.hasWriteUsage( this ) === false ) {

					const snippet = this.placeholderNode.build( builder, this.getNodeType( builder ) );

					builder.addLineFlowCode( `${ builder.getPropertyName( nodeVar ) } = ${ snippet }`, this );

				}

			}

		}

		return builder.getPropertyName( nodeVar );

	}

}

/**
 * TSL function for creating a property node.
 *
 * @tsl
 * @function
 * @param {string} type - The type of the node.
 * @param {?string} [name=null] - The name of the property in the shader.
 * @param {?Node} [placeholderNode=null] - The placeholder node if not assigned.
 * @returns {PropertyNode}
 */
const property = ( type, name, placeholderNode = null ) => new PropertyNode( type, name, false, placeholderNode );

/**
 * TSL function for creating a varying property node.
 *
 * @tsl
 * @function
 * @param {string} type - The type of the node.
 * @param {?string} [name=null] - The name of the varying in the shader.
 * @param {?Node} [placeholderNode=null] - The placeholder node if not assigned.
 * @returns {PropertyNode}
 */
const varyingProperty = ( type, name, placeholderNode = null ) => new PropertyNode( type, name, true, placeholderNode );

/**
 * TSL object that represents the shader variable `DiffuseColor`.
 *
 * @tsl
 * @type {PropertyNode<vec4>}
 */
const diffuseColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec4', 'DiffuseColor' );

/**
 * TSL object that represents the shader variable `DiffuseContribution`.
 *
 * @tsl
 * @type {PropertyNode<vec3>}
 */
const diffuseContribution = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'DiffuseContribution' );

/**
 * TSL object that represents the shader variable `DiffuseRoughness`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const diffuseRoughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'DiffuseRoughness' );

/**
 * TSL object that represents the shader variable `EmissiveColor`.
 *
 * @tsl
 * @type {PropertyNode<vec3>}
 */
const emissive = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'EmissiveColor' );

/**
 * TSL object that represents the shader variable `Roughness`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const roughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Roughness' );

/**
 * TSL object that represents the shader variable `Metalness`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const metalness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Metalness' );

/**
 * TSL object that represents the shader variable `Clearcoat`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const clearcoat = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Clearcoat' );

/**
 * TSL object that represents the shader variable `ClearcoatRoughness`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const clearcoatRoughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'ClearcoatRoughness' );

/**
 * TSL object that represents the shader variable `Sheen`.
 *
 * @tsl
 * @type {PropertyNode<vec3>}
 */
const sheen = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'Sheen' );

/**
 * TSL object that represents the shader variable `SheenRoughness`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const sheenRoughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'SheenRoughness' );

/**
 * TSL object that represents the shader variable `Iridescence`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const iridescence = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Iridescence' );

/**
 * TSL object that represents the shader variable `IridescenceIOR`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const iridescenceIOR = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IridescenceIOR' );

/**
 * TSL object that represents the shader variable `IridescenceThickness`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const iridescenceThickness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IridescenceThickness' );

/**
 * TSL object that represents the shader variable `AlphaT`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const alphaT = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AlphaT' );

/**
 * TSL object that represents the shader variable `Anisotropy`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const anisotropy = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Anisotropy' );

/**
 * TSL object that represents the shader variable `AnisotropyT`.
 *
 * @tsl
 * @type {PropertyNode<vec3>}
 */
const anisotropyT = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'AnisotropyT' );

/**
 * TSL object that represents the shader variable `AnisotropyB`.
 *
 * @tsl
 * @type {PropertyNode<vec3>}
 */
const anisotropyB = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'AnisotropyB' );

/**
 * TSL object that represents the shader variable `SpecularColor`.
 *
 * @tsl
 * @type {PropertyNode<color>}
 */
const specularColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'SpecularColor' );

/**
 * TSL object that represents the shader variable `SpecularColorBlended`.
 *
 * @tsl
 * @type {PropertyNode<color>}
 */
const specularColorBlended = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'SpecularColorBlended' );

/**
 * TSL object that represents the shader variable `SpecularF90`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const specularF90 = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'SpecularF90' );

/**
 * TSL object that represents the shader variable `Shininess`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const shininess = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Shininess' );

/**
 * TSL object that represents the shader variable `Output`.
 *
 * @tsl
 * @type {PropertyNode<vec4>}
 */
const output = /*@__PURE__*/ nodeImmutable( PropertyNode, 'output', 'Output' );

/**
 * TSL object that represents the shader variable `dashSize`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const dashSize = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'dashSize' );

/**
 * TSL object that represents the shader variable `gapSize`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const gapSize = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'gapSize' );

/**
 * TSL object that represents the shader variable `pointWidth`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const pointWidth = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'pointWidth' );

/**
 * TSL object that represents the shader variable `IOR`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const ior = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IOR' );

/**
 * TSL object that represents the shader variable `Transmission`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const transmission = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Transmission' );

/**
 * TSL object that represents the shader variable `Thickness`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const thickness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Thickness' );

/**
 * TSL object that represents the shader variable `AttenuationDistance`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const attenuationDistance = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AttenuationDistance' );

/**
 * TSL object that represents the shader variable `AttenuationColor`.
 *
 * @tsl
 * @type {PropertyNode<color>}
 */
const attenuationColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'AttenuationColor' );

/**
 * TSL object that represents the shader variable `Dispersion`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const dispersion = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Dispersion' );

/**
 * TSL object that represents the shader variable `Retroreflectivity`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const retroreflectivity = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Retroreflectivity' );

/**
 * TSL object that represents the shader variable `AmbientOcclusion`.
 * If no value is assigned to this property, it defaults to a placeholder value of `1.0`.
 *
 * @tsl
 * @type {PropertyNode<float>}
 */
const ambientOcclusion = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AmbientOcclusion', false, 1 );

/**
 * This module represents the call of a {@link FunctionNode}. Developers are usually not confronted
 * with this module since they use the predefined TSL syntax `wgslFn` and `glslFn` which encapsulate
 * this logic.
 *
 * @augments Node
 */
class FunctionCallNode extends Node {

	static get type() {

		return 'FunctionCallNode';

	}

	/**
	 * Constructs a new function call node.
	 *
	 * @param {?FunctionNode} functionNode - The function node.
	 * @param {Object<string, Node>} [parameters={}] - The parameters for the function call.
	 */
	constructor( functionNode = null, parameters = {} ) {

		super();

		/**
		 * The function node.
		 *
		 * @type {?FunctionNode}
		 * @default null
		 */
		this.functionNode = functionNode;

		/**
		 * The parameters of the function call.
		 *
		 * @type {Object<string, Node>}
		 * @default {}
		 */
		this.parameters = parameters;

	}

	/**
	 * Sets the parameters of the function call node.
	 *
	 * @param {Object<string, Node>} parameters - The parameters to set.
	 * @return {FunctionCallNode} A reference to this node.
	 */
	setParameters( parameters ) {

		this.parameters = parameters;

		return this;

	}

	/**
	 * Returns the parameters of the function call node.
	 *
	 * @return {Object<string, Node>} The parameters of this node.
	 */
	getParameters() {

		return this.parameters;

	}

	/**
	 * Returns the type of this function call node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @returns {string} The type of this node.
	 */
	generateNodeType( builder ) {

		return this.functionNode.getNodeType( builder );

	}

	/**
	 * Returns the function node of this function call node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} [name] - The name of the member.
	 * @returns {string} The type of the member.
	 */
	getMemberType( builder, name ) {

		return this.functionNode.getMemberType( builder, name );

	}

	generate( builder ) {

		const params = [];

		const functionNode = this.functionNode;

		const inputs = functionNode.getInputs( builder );
		const parameters = this.parameters;

		const generateInput = ( node, inputNode ) => {

			const type = inputNode.type;
			const pointer = type === 'pointer';

			let output;

			if ( pointer ) output = '&' + node.build( builder );
			else output = node.build( builder, type );

			return output;

		};

		if ( Array.isArray( parameters ) ) {

			if ( parameters.length > inputs.length ) {

				error( 'TSL: The number of provided parameters exceeds the expected number of inputs in \'Fn()\'.' );

				parameters.length = inputs.length;

			} else if ( parameters.length < inputs.length ) {

				error( 'TSL: The number of provided parameters is less than the expected number of inputs in \'Fn()\'.' );

				while ( parameters.length < inputs.length ) {

					parameters.push( float( 0 ) );

				}

			}

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

				params.push( generateInput( parameters[ i ], inputs[ i ] ) );

			}

		} else {

			for ( const inputNode of inputs ) {

				const node = parameters[ inputNode.name ];

				if ( node !== undefined ) {

					params.push( generateInput( node, inputNode ) );

				} else {

					error( `TSL: Input '${ inputNode.name }' not found in \'Fn()\'.` );

					params.push( generateInput( float( 0 ), inputNode ) );

				}

			}

		}

		const functionName = functionNode.build( builder, 'property' );

		return `${ functionName }( ${ params.join( ', ' ) } )`;

	}

}

const call = ( func, ...params ) => {

	params = params.length > 1 || ( params[ 0 ] && params[ 0 ].isNode === true ) ? nodeArray( params ) : nodeObjects( params[ 0 ] );

	return new FunctionCallNode( nodeObject( func ), params );

};

addMethodChaining( 'call', call );

const _vectorOperators = {
	'==': 'equal',
	'!=': 'notEqual',
	'<': 'lessThan',
	'>': 'greaterThan',
	'<=': 'lessThanEqual',
	'>=': 'greaterThanEqual',
	'%': 'mod'
};

/**
 * This node represents basic mathematical and logical operations like addition,
 * subtraction or comparisons (e.g. `equal()`).
 *
 * @augments Node
 */
class OperatorNode extends Node {

	static get type() {

		return 'OperatorNode';

	}

	/**
	 * Constructs a new operator node.
	 *
	 * @param {string} op - The operator.
	 * @param {Node} aNode - The first input.
	 * @param {Node} bNode - The second input.
	 * @param {...Node} params - Additional input parameters.
	 */
	constructor( op, aNode, bNode, ...params ) {

		super();

		if ( params.length > 0 ) {

			let finalOp = new OperatorNode( op, aNode, bNode );

			for ( let i = 0; i < params.length - 1; i ++ ) {

				finalOp = new OperatorNode( op, finalOp, params[ i ] );

			}

			aNode = finalOp;
			bNode = params[ params.length - 1 ];

		}

		/**
		 * The operator.
		 *
		 * @type {string}
		 */
		this.op = op;

		/**
		 * The first input.
		 *
		 * @type {Node}
		 */
		this.aNode = aNode;

		/**
		 * The second input.
		 *
		 * @type {Node}
		 */
		this.bNode = bNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isOperatorNode = true;

	}

	/**
	 * Returns the operator method name.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} output - The output type.
	 * @returns {string} The operator method name.
	 */
	getOperatorMethod( builder, output ) {

		return builder.getMethod( _vectorOperators[ this.op ], output );

	}

	/**
	 * This method is overwritten since the node type is inferred from the operator
	 * and the input node types.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {?string} [output=null] - The output type.
	 * @return {string} The node type.
	 */
	generateNodeType( builder, output = null ) {

		const op = this.op;

		const aNode = this.aNode;
		const bNode = this.bNode;

		const typeA = aNode.getNodeType( builder );
		const typeB = bNode ? bNode.getNodeType( builder ) : null;

		if ( typeA === 'void' || typeB === 'void' ) {

			return output || 'void';

		} else if ( op === '~' || op === '&' || op === '|' || op === '^' || op === '>>' || op === '<<' ) {

			return builder.getIntegerType( typeA );

		} else if ( op === '&&' || op === '||' || op === '^^' ) {

			return 'bool';

		} else if ( op === '!' ) {

			const typeLength = builder.getTypeLength( typeA );

			return typeLength > 1 ? `bvec${ typeLength }` : 'bool';

		} else if ( op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=' ) {

			const typeLength = Math.max( builder.getTypeLength( typeA ), builder.getTypeLength( typeB ) );

			return typeLength > 1 ? `bvec${ typeLength }` : 'bool';

		} else if ( typeB === null ) {

			return typeA;

		} else {

			// Handle matrix operations

			if ( builder.isMatrix( typeA ) ) {

				if ( builder.isScalar( typeB ) ) {

					return typeA; // matrix * scalar = matrix

				} else if ( builder.isVector( typeB ) ) {

					return builder.getVectorFromMatrix( typeA ); // matrix * vector

				} else if ( builder.isMatrix( typeB ) ) {

					return typeA; // matrix * matrix

				}

			} else if ( builder.isMatrix( typeB ) ) {

				if ( builder.isScalar( typeA ) ) {

					return typeB; // scalar * matrix = matrix

				} else if ( builder.isVector( typeA ) ) {

					return builder.getVectorFromMatrix( typeB ); // vector * matrix

				}

			}

			// Handle non-matrix cases

			// anytype x anytype: use the greater length vector

			const type = builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ? typeB : typeA;

			const promotedType = builder.getPromotedComponentType( aNode, bNode );

			return builder.changeComponentType( type, promotedType );

		}

	}

	generate( builder, output ) {

		const op = this.op;

		const { aNode, bNode } = this;

		const type = this.getNodeType( builder, output );

		let typeA = null;
		let typeB = null;

		if ( type !== 'void' ) {

			typeA = aNode.getNodeType( builder );
			typeB = bNode ? bNode.getNodeType( builder ) : null;

			if ( op === '<' || op === '>' || op === '<=' || op === '>=' || op === '==' || op === '!=' ) {

				const length = Math.max( builder.getTypeLength( typeA ), builder.getTypeLength( typeB ) );

				typeA = typeB = builder.getTypeFromLength( length, builder.getPromotedComponentType( aNode, bNode ) );

			} else if ( op === '>>' || op === '<<' ) {

				typeA = type;
				typeB = builder.changeComponentType( typeB, 'uint' );

			} else if ( builder.isMatrix( typeA ) ) {

				if ( builder.isScalar( typeB ) ) {

					// Keep matrix type for typeA, but ensure typeB stays float

					typeB = 'float';

				} else if ( builder.isVector( typeB ) ) {

					// matrix x vector
					typeB = builder.getVectorFromMatrix( typeA );

				} else if ( builder.isMatrix( typeB ) ) ; else {

					typeA = typeB = type;

				}

			} else if ( builder.isMatrix( typeB ) ) {

				if ( builder.isScalar( typeA ) ) {

					// Keep matrix type for typeB, but ensure typeA stays float

					typeA = 'float';

				} else if ( builder.isVector( typeA ) ) {

					// vector x matrix

					typeA = builder.getVectorFromMatrix( typeB );

				} else {

					typeA = typeB = type;

				}

			} else {

				// anytype x anytype

				typeA = typeB = type;

			}

		} else {

			typeA = typeB = type;

		}

		const a = aNode.build( builder, typeA );
		const b = bNode ? bNode.build( builder, typeB ) : null;

		const fnOpSnippet = builder.getFunctionOperator( op );

		if ( output !== 'void' ) {

			const isGLSL = builder.renderer.coordinateSystem === WebGLCoordinateSystem;

			if ( op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=' ) {

				if ( isGLSL ) {

					if ( builder.isVector( typeA ) ) {

						return builder.format( `${ this.getOperatorMethod( builder, output ) }( ${ a }, ${ b } )`, type, output );

					} else {

						return builder.format( `( ${ a } ${ op } ${ b } )`, type, output );

					}

				} else {

					// WGSL

					return builder.format( `( ${ a } ${ op } ${ b } )`, type, output );

				}

			} else if ( op === '%' ) {

				if ( builder.isInteger( typeB ) ) {

					return builder.format( `( ${ a } % ${ b } )`, type, output );

				} else {

					return builder.format( `${ this.getOperatorMethod( builder, type ) }( ${ a }, ${ b } )`, type, output );

				}

			} else if ( op === '!' ) {

				if ( isGLSL && builder.isVector( typeA ) ) {

					return builder.format( `not( ${a} )`, output );

				} else {

					// WGSL and scalars on GLSL

					return builder.format( `( ${op} ${a} )`, typeA, output );

				}

			} else if ( op === '~' ) {

				return builder.format( `( ${op} ${a} )`, typeA, output );

			} else if ( fnOpSnippet ) {

				return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output );

			} else {

				// Handle matrix operations

				if ( builder.isMatrix( typeA ) && builder.isScalar( typeB ) ) {

					return builder.format( `( ${ b } ${ op } ${ a } )`, type, output );

				} else if ( builder.isScalar( typeA ) && builder.isMatrix( typeB ) ) {

					return builder.format( `${ a } ${ op } ${ b }`, type, output );

				} else {

					let snippet = `( ${ a } ${ op } ${ b } )`;

					if ( ! isGLSL && type === 'bool' && builder.isVector( typeA ) && builder.isVector( typeB ) ) {

						snippet = `all${ snippet }`;

					}

					return builder.format( snippet, type, output );

				}

			}

		} else if ( typeA !== 'void' ) {

			if ( fnOpSnippet ) {

				return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output );

			} else {

				if ( builder.isMatrix( typeA ) && builder.isScalar( typeB ) ) {

					return builder.format( `${ b } ${ op } ${ a }`, type, output );

				} else {

					return builder.format( `${ a } ${ op } ${ b }`, type, output );

				}

			}

		}

	}

	serialize( data ) {

		super.serialize( data );

		data.op = this.op;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.op = data.op;

	}

}

/**
 * Returns the addition of two or more value.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @param {...Node} params - Additional input parameters.
 * @returns {OperatorNode}
 */
const add = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '+' ).setParameterLength( 2, Infinity ).setName( 'add' );

/**
 * Returns the subtraction of two or more value.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @param {...Node} params - Additional input parameters.
 * @returns {OperatorNode}
 */
const sub = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '-' ).setParameterLength( 2, Infinity ).setName( 'sub' );

/**
 * Returns the multiplication of two or more value.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @param {...Node} params - Additional input parameters.
 * @returns {OperatorNode}
 */
const mul = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '*' ).setParameterLength( 2, Infinity ).setName( 'mul' );

/**
 * Returns the division of two or more value.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @param {...Node} params - Additional input parameters.
 * @returns {OperatorNode}
 */
const div = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '/' ).setParameterLength( 2, Infinity ).setName( 'div' );

/**
 * Computes the remainder of dividing the first node by the second one.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const mod = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '%' ).setParameterLength( 2 ).setName( 'mod' );

/**
 * Checks if two nodes are equal.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const equal = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '==' ).setParameterLength( 2 ).setName( 'equal' );

/**
 * Checks if two nodes are not equal.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const notEqual = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '!=' ).setParameterLength( 2 ).setName( 'notEqual' );

/**
 * Checks if the first node is less than the second.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const lessThan = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '<' ).setParameterLength( 2 ).setName( 'lessThan' );

/**
 * Checks if the first node is greater than the second.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const greaterThan = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '>' ).setParameterLength( 2 ).setName( 'greaterThan' );

/**
 * Checks if the first node is less than or equal to the second.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const lessThanEqual = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '<=' ).setParameterLength( 2 ).setName( 'lessThanEqual' );

/**
 * Checks if the first node is greater than or equal to the second.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const greaterThanEqual = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '>=' ).setParameterLength( 2 ).setName( 'greaterThanEqual' );

/**
 * Performs a logical AND operation on multiple nodes.
 *
 * @tsl
 * @function
 * @param {...Node} nodes - The input nodes to be combined using AND.
 * @returns {OperatorNode}
 */
const and = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '&&' ).setParameterLength( 2, Infinity ).setName( 'and' );

/**
 * Performs a logical OR operation on multiple nodes.
 *
 * @tsl
 * @function
 * @param {...Node} nodes - The input nodes to be combined using OR.
 * @returns {OperatorNode}
 */
const or = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '||' ).setParameterLength( 2, Infinity ).setName( 'or' );

/**
 * Performs logical NOT on a node.
 *
 * @tsl
 * @function
 * @param {Node} value - The value.
 * @returns {OperatorNode}
 */
const not = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '!' ).setParameterLength( 1 ).setName( 'not' );

/**
 * Performs logical XOR on two nodes.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const xor = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '^^' ).setParameterLength( 2 ).setName( 'xor' );

/**
 * Performs bitwise AND on two nodes.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const bitAnd = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '&' ).setParameterLength( 2 ).setName( 'bitAnd' );

/**
 * Performs bitwise NOT on a node.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const bitNot = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '~' ).setParameterLength( 1 ).setName( 'bitNot' );

/**
 * Performs bitwise OR on two nodes.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const bitOr = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '|' ).setParameterLength( 2 ).setName( 'bitOr' );

/**
 * Performs bitwise XOR on two nodes.
 *
 * @tsl
 * @function
 * @param {Node} a - The first input.
 * @param {Node} b - The second input.
 * @returns {OperatorNode}
 */
const bitXor = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '^' ).setParameterLength( 2 ).setName( 'bitXor' );

/**
 * Shifts a node to the left.
 *
 * @tsl
 * @function
 * @param {Node} a - The node to shift.
 * @param {Node} b - The value to shift.
 * @returns {OperatorNode}
 */
const shiftLeft = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '<<' ).setParameterLength( 2 ).setName( 'shiftLeft' );

/**
 * Shifts a node to the right.
 *
 * @tsl
 * @function
 * @param {Node} a - The node to shift.
 * @param {Node} b - The value to shift.
 * @returns {OperatorNode}
 */
const shiftRight = /*@__PURE__*/ nodeProxyAssignable( OperatorNode, '>>' ).setParameterLength( 2 ).setName( 'shiftRight' );

/**
 * Increments a node by 1.
 *
 * @tsl
 * @function
 * @param {Node} a - The node to increment.
 * @returns {OperatorNode}
 */
const incrementBefore = /*@__PURE__*/ Fn( ( [ a ] ) => {

	a.addAssign( 1 );
	return a;

} );

/**
 * Decrements a node by 1.
 *
 * @tsl
 * @function
 * @param {Node} a - The node to decrement.
 * @returns {OperatorNode}
 */
const decrementBefore = /*@__PURE__*/ Fn( ( [ a ] ) => {

	a.subAssign( 1 );
	return a;

} );

/**
 * Increments a node by 1 and returns the previous value.
 *
 * @tsl
 * @function
 * @param {Node} a - The node to increment.
 * @returns {OperatorNode}
 */
const increment = /*@__PURE__*/ Fn( ( [ a ] ) => {

	const temp = int( a ).toConst();
	a.addAssign( 1 );
	return temp;

} );

/**
 * Decrements a node by 1 and returns the previous value.
 *
 * @tsl
 * @function
 * @param {Node} a - The node to decrement.
 * @returns {OperatorNode}
 */
const decrement = /*@__PURE__*/ Fn( ( [ a ] ) => {

	const temp = int( a ).toConst();
	a.subAssign( 1 );
	return temp;

} );

addMethodChaining( 'add', add );
addMethodChaining( 'sub', sub );
addMethodChaining( 'mul', mul );
addMethodChaining( 'div', div );
addMethodChaining( 'mod', mod );
addMethodChaining( 'equal', equal );
addMethodChaining( 'notEqual', notEqual );
addMethodChaining( 'lessThan', lessThan );
addMethodChaining( 'greaterThan', greaterThan );
addMethodChaining( 'lessThanEqual', lessThanEqual );
addMethodChaining( 'greaterThanEqual', greaterThanEqual );
addMethodChaining( 'and', and );
addMethodChaining( 'or', or );
addMethodChaining( 'not', not );
addMethodChaining( 'xor', xor );
addMethodChaining( 'bitAnd', bitAnd );
addMethodChaining( 'bitNot', bitNot );
addMethodChaining( 'bitOr', bitOr );
addMethodChaining( 'bitXor', bitXor );
addMethodChaining( 'shiftLeft', shiftLeft );
addMethodChaining( 'shiftRight', shiftRight );

addMethodChaining( 'incrementBefore', incrementBefore );
addMethodChaining( 'decrementBefore', decrementBefore );
addMethodChaining( 'increment', increment );
addMethodChaining( 'decrement', decrement );

/**
 * This node represents a variety of mathematical methods available in shaders.
 * They are divided into three categories:
 *
 * - Methods with one input like `sin`, `cos` or `normalize`.
 * - Methods with two inputs like `dot`, `cross` or `pow`.
 * - Methods with three inputs like `mix`, `clamp` or `smoothstep`.
 *
 * @augments Node
 */
class MathNode extends Node {

	static get type() {

		return 'MathNode';

	}

	/**
	 * Constructs a new math node.
	 *
	 * @param {string} method - The method name.
	 * @param {Node} aNode - The first input.
	 * @param {?Node} [bNode=null] - The second input.
	 * @param {?Node} [cNode=null] - The third input.
	 */
	constructor( method, aNode, bNode = null, cNode = null ) {

		super();

		// Allow the max() and min() functions to take an arbitrary number of arguments.

		if ( ( method === MathNode.MAX || method === MathNode.MIN ) && arguments.length > 3 ) {

			let finalOp = new MathNode( method, aNode, bNode );

			for ( let i = 3; i < arguments.length - 1; i ++ ) {

				finalOp = new MathNode( method, finalOp, arguments[ i ] );

			}

			aNode = finalOp;
			bNode = arguments[ arguments.length - 1 ];
			cNode = null;

		}

		/**
		 * The method name.
		 *
		 * @type {string}
		 */
		this.method = method;

		/**
		 * The first input.
		 *
		 * @type {Node}
		 */
		this.aNode = aNode;

		/**
		 * The second input.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.bNode = bNode;

		/**
		 * The third input.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.cNode = cNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isMathNode = true;

	}

	/**
	 * The input type is inferred from the node types of the input nodes.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( builder ) {

		const aType = this.aNode.getNodeType( builder );
		const bType = this.bNode ? this.bNode.getNodeType( builder ) : null;
		const cType = this.cNode ? this.cNode.getNodeType( builder ) : null;

		const aLen = builder.isMatrix( aType ) ? 0 : builder.getTypeLength( aType );
		const bLen = builder.isMatrix( bType ) ? 0 : builder.getTypeLength( bType );
		const cLen = builder.isMatrix( cType ) ? 0 : builder.getTypeLength( cType );

		let type;

		if ( aLen > bLen && aLen > cLen ) {

			type = aType;

		} else if ( bLen > cLen ) {

			type = bType;

		} else if ( cLen > aLen ) {

			type = cType;

		} else {

			type = aType;

		}

		if ( builder.isMatrix( type ) ) return type;

		const promotedType = _floatMethods.has( this.method ) ? 'float' : builder.getPromotedComponentType( this.aNode, this.bNode, this.cNode );

		return builder.changeComponentType( type, promotedType );

	}

	/**
	 * The selected method as well as the input type determine the node type of this node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		const method = this.method;

		if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT || method === MathNode.DETERMINANT ) {

			return 'float';

		} else if ( method === MathNode.CROSS ) {

			return 'vec3';

		} else if ( method === MathNode.ALL || method === MathNode.ANY ) {

			return 'bool';

		} else if ( method === MathNode.EQUALS ) {

			return builder.changeComponentType( this.aNode.getNodeType( builder ), 'bool' );

		} else {

			return this.getInputType( builder );

		}

	}

	setup( builder ) {

		const { aNode, bNode, method } = this;

		let outputNode = null;

		if ( method === MathNode.ONE_MINUS ) {

			outputNode = sub( 1.0, aNode );

		} else if ( method === MathNode.RECIPROCAL ) {

			outputNode = div( float( 1 ), aNode );

		} else if ( method === MathNode.DIFFERENCE ) {

			outputNode = abs( sub( aNode, bNode ) );

		} else if ( method === MathNode.TRANSFORM_DIRECTION ) {

			// pre-multiplies the direction by the matrix and normalizes the result

			let matrixNode, directionNode;

			if ( builder.isMatrix( aNode.getNodeType( builder ) ) ) {

				matrixNode = aNode;
				directionNode = bNode;

			} else {

				matrixNode = bNode;
				directionNode = aNode;

			}

			outputNode = normalize( mul( matrixNode, vec4( vec3( directionNode ), 0.0 ) ).xyz );

		}

		if ( outputNode !== null ) {

			return outputNode;

		} else {

			return super.setup( builder );

		}

	}

	generate( builder, output ) {

		const properties = builder.getNodeProperties( this );

		if ( properties.outputNode ) {

			return super.generate( builder, output );

		}

		let method = this.method;

		const type = this.getNodeType( builder );
		const inputType = this.getInputType( builder );

		const a = this.aNode;
		const b = this.bNode;
		const c = this.cNode;

		const coordinateSystem = builder.renderer.coordinateSystem;

		if ( method === MathNode.NEGATE ) {

			return builder.format( '( - ' + a.build( builder, inputType ) + ' )', type, output );

		} else {

			const params = [];

			if ( method === MathNode.CROSS ) {

				params.push(
					a.build( builder, type ),
					b.build( builder, type )
				);

			} else if ( coordinateSystem === WebGLCoordinateSystem && method === MathNode.STEP ) {

				params.push(
					a.build( builder, builder.getTypeLength( a.getNodeType( builder ) ) === 1 ? 'float' : inputType ),
					b.build( builder, inputType )
				);

			} else if ( coordinateSystem === WebGLCoordinateSystem && ( method === MathNode.MIN || method === MathNode.MAX ) ) {

				params.push(
					a.build( builder, inputType ),
					b.build( builder, builder.getTypeLength( b.getNodeType( builder ) ) === 1 ? builder.getComponentType( inputType ) : inputType )
				);

			} else if ( method === MathNode.REFRACT ) {

				params.push(
					a.build( builder, inputType ),
					b.build( builder, inputType ),
					c.build( builder, 'float' )
				);

			} else if ( method === MathNode.MIX ) {

				params.push(
					a.build( builder, inputType ),
					b.build( builder, inputType ),
					c.build( builder, builder.getTypeLength( c.getNodeType( builder ) ) === 1 ? 'float' : inputType )
				);

			} else {

				if ( coordinateSystem === WebGPUCoordinateSystem && method === MathNode.ATAN && b !== null ) {

					method = 'atan2';

				}

				if ( builder.shaderStage !== 'fragment' && ( method === MathNode.DFDX || method === MathNode.DFDY ) ) {

					error( `TSL: '${ method }' is not supported in the ${ builder.shaderStage } stage.`, this.stackTrace );

					method = '/*' + method + '*/';

				}

				params.push( a.build( builder, inputType ) );
				if ( b !== null ) params.push( b.build( builder, inputType ) );
				if ( c !== null ) params.push( c.build( builder, inputType ) );

			}

			return builder.format( `${ builder.getMethod( method, type ) }( ${params.join( ', ' )} )`, type, output );

		}

	}

	serialize( data ) {

		super.serialize( data );

		data.method = this.method;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.method = data.method;

	}

}

// 1 input

MathNode.ALL = 'all';
MathNode.ANY = 'any';

MathNode.RADIANS = 'radians';
MathNode.DEGREES = 'degrees';
MathNode.EXP = 'exp';
MathNode.EXP2 = 'exp2';
MathNode.LOG = 'log';
MathNode.LOG2 = 'log2';
MathNode.SQRT = 'sqrt';
MathNode.INVERSE_SQRT = 'inversesqrt';
MathNode.FLOOR = 'floor';
MathNode.CEIL = 'ceil';
MathNode.NORMALIZE = 'normalize';
MathNode.FRACT = 'fract';
MathNode.SIN = 'sin';
MathNode.SINH = 'sinh';
MathNode.COS = 'cos';
MathNode.COSH = 'cosh';
MathNode.TAN = 'tan';
MathNode.TANH = 'tanh';
MathNode.ASIN = 'asin';
MathNode.ASINH = 'asinh';
MathNode.ACOS = 'acos';
MathNode.ACOSH = 'acosh';
MathNode.ATAN = 'atan';
MathNode.ATANH = 'atanh';
MathNode.ABS = 'abs';
MathNode.SIGN = 'sign';
MathNode.LENGTH = 'length';
MathNode.NEGATE = 'negate';
MathNode.ONE_MINUS = 'oneMinus';
MathNode.DFDX = 'dFdx';
MathNode.DFDY = 'dFdy';
MathNode.ROUND = 'round';
MathNode.RECIPROCAL = 'reciprocal';
MathNode.TRUNC = 'trunc';
MathNode.FWIDTH = 'fwidth';
MathNode.TRANSPOSE = 'transpose';
MathNode.DETERMINANT = 'determinant';
MathNode.INVERSE = 'inverse';

// 2 inputs

MathNode.EQUALS = 'equals';
MathNode.MIN = 'min';
MathNode.MAX = 'max';
MathNode.STEP = 'step';
MathNode.REFLECT = 'reflect';
MathNode.DISTANCE = 'distance';
MathNode.DIFFERENCE = 'difference';
MathNode.DOT = 'dot';
MathNode.CROSS = 'cross';
MathNode.POW = 'pow';
MathNode.TRANSFORM_DIRECTION = 'transformDirection';

// 3 inputs

MathNode.MIX = 'mix';
MathNode.CLAMP = 'clamp';
MathNode.REFRACT = 'refract';
MathNode.SMOOTHSTEP = 'smoothstep';
MathNode.FACEFORWARD = 'faceforward';

// Methods that are only defined for floating-point types.

const _floatMethods = new Set( [
	MathNode.RADIANS, MathNode.DEGREES, MathNode.EXP, MathNode.EXP2, MathNode.LOG, MathNode.LOG2,
	MathNode.SQRT, MathNode.INVERSE_SQRT, MathNode.FLOOR, MathNode.CEIL, MathNode.NORMALIZE, MathNode.FRACT,
	MathNode.SIN, MathNode.SINH, MathNode.COS, MathNode.COSH, MathNode.TAN, MathNode.TANH,
	MathNode.ASIN, MathNode.ASINH, MathNode.ACOS, MathNode.ACOSH, MathNode.ATAN, MathNode.ATANH,
	MathNode.LENGTH, MathNode.DFDX, MathNode.DFDY, MathNode.ROUND, MathNode.TRUNC, MathNode.FWIDTH, MathNode.RECIPROCAL,
	MathNode.STEP, MathNode.REFLECT, MathNode.DISTANCE, MathNode.DOT, MathNode.CROSS, MathNode.POW,
	MathNode.MIX, MathNode.REFRACT, MathNode.SMOOTHSTEP, MathNode.FACEFORWARD
] );

// 1 inputs

/**
 * A small value used to handle floating-point precision errors.
 *
 * @tsl
 * @type {Node<float>}
 */
const EPSILON = /*@__PURE__*/ float( 1e-6 );

/**
 * Represents infinity.
 *
 * @tsl
 * @type {Node<float>}
 */
const INFINITY = /*@__PURE__*/ float( 1e6 );

/**
 * Represents PI.
 *
 * @tsl
 * @type {Node<float>}
 */
const PI = /*@__PURE__*/ float( Math.PI );

/**
 * Represents PI * 2. Please use the non-deprecated version `TWO_PI`.
 *
 * @tsl
 * @deprecated
 * @type {Node<float>}
 */
const PI2 = /*@__PURE__*/ float( Math.PI * 2 ); // @deprecated r181

/**
 * Represents PI * 2.
 *
 * @tsl
 * @type {Node<float>}
 */
const TWO_PI = /*@__PURE__*/ float( Math.PI * 2 );

/**
 * Represents PI / 2.
 *
 * @tsl
 * @type {Node<float>}
 */
const HALF_PI = /*@__PURE__*/ float( Math.PI * 0.5 );

/**
 * Returns `true` if all components of `x` are `true`.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node<bool>}
 */
const all = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ALL ).setParameterLength( 1 );

/**
 * Returns `true` if any components of `x` are `true`.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node<bool>}
 */
const any = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ANY ).setParameterLength( 1 );

/**
 * Converts a quantity in degrees to radians.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The input in degrees.
 * @returns {Node}
 */
const radians = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.RADIANS ).setParameterLength( 1 );

/**
 * Convert a quantity in radians to degrees.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The input in radians.
 * @returns {Node}
 */
const degrees = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.DEGREES ).setParameterLength( 1 );

/**
 * Returns the natural exponentiation of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const exp = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.EXP ).setParameterLength( 1 );

/**
 * Returns 2 raised to the power of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const exp2 = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.EXP2 ).setParameterLength( 1 );

/**
 * Returns the natural logarithm of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const log = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.LOG ).setParameterLength( 1 );

/**
 * Returns the base 2 logarithm of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const log2 = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.LOG2 ).setParameterLength( 1 );

/**
 * Returns the square root of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const sqrt = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.SQRT ).setParameterLength( 1 );

/**
 * Returns the inverse of the square root of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const inverseSqrt = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.INVERSE_SQRT ).setParameterLength( 1 );

/**
 * Finds the nearest integer less than or equal to the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const floor = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.FLOOR ).setParameterLength( 1 );

/**
 * Finds the nearest integer that is greater than or equal to the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const ceil = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.CEIL ).setParameterLength( 1 );

/**
 * Calculates the unit vector in the same direction as the original vector.
 *
 * @tsl
 * @function
 * @param {Node} x - The input vector.
 * @returns {Node}
 */
const normalize = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.NORMALIZE ).setParameterLength( 1 );

/**
 * Computes the fractional part of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const fract = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.FRACT ).setParameterLength( 1 );

/**
 * Returns the sine of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const sin = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.SIN ).setParameterLength( 1 );

/**
 * Returns the hyperbolic sine of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const sinh = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.SINH ).setParameterLength( 1 );

/**
 * Returns the cosine of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const cos = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.COS ).setParameterLength( 1 );

/**
 * Returns the hyperbolic cosine of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const cosh = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.COSH ).setParameterLength( 1 );

/**
 * Returns the tangent of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const tan = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.TAN ).setParameterLength( 1 );

/**
 * Returns the hyperbolic tangent of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const tanh = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.TANH ).setParameterLength( 1 );

/**
 * Returns the arcsine of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const asin = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ASIN ).setParameterLength( 1 );

/**
 * Returns the inverse hyperbolic sine of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const asinh = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ASINH ).setParameterLength( 1 );

/**
 * Returns the arccosine of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const acos = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ACOS ).setParameterLength( 1 );

/**
 * Returns the inverse hyperbolic cosine of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const acosh = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ACOSH ).setParameterLength( 1 );

/**
 * Returns the arc-tangent of the parameter.
 * If two parameters are provided, the result is `atan2(y/x)`.
 *
 * @tsl
 * @function
 * @param {Node | number} y - The y parameter.
 * @param {?(Node | number)} x - The x parameter.
 * @returns {Node}
 */
const atan = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ATAN ).setParameterLength( 1, 2 );

/**
 * Returns the inverse hyperbolic tangent of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const atanh = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ATANH ).setParameterLength( 1 );

/**
 * Returns the absolute value of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const abs = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ABS ).setParameterLength( 1 );

/**
 * Extracts the sign of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const sign = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.SIGN ).setParameterLength( 1 );

/**
 * Calculates the length of a vector.
 *
 * @tsl
 * @function
 * @param {Node} x - The parameter.
 * @returns {Node<float>}
 */
const length = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.LENGTH ).setParameterLength( 1 );

/**
 * Negates the value of the parameter (-x).
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const negate = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.NEGATE ).setParameterLength( 1 );

/**
 * Return `1` minus the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const oneMinus = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ONE_MINUS ).setParameterLength( 1 );

/**
 * Returns the partial derivative of the parameter with respect to x.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const dFdx = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.DFDX ).setParameterLength( 1 );

/**
 * Returns the partial derivative of the parameter with respect to y.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const dFdy = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.DFDY ).setParameterLength( 1 );

/**
 * Rounds the parameter to the nearest integer.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const round = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.ROUND ).setParameterLength( 1 );

/**
 * Returns the reciprocal of the parameter `(1/x)`.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const reciprocal = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.RECIPROCAL ).setParameterLength( 1 );

/**
 * Truncates the parameter, removing the fractional part.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const trunc = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.TRUNC ).setParameterLength( 1 );

/**
 * Returns the sum of the absolute derivatives in x and y.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @returns {Node}
 */
const fwidth = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.FWIDTH ).setParameterLength( 1 );

/**
 * Returns the transpose of a matrix.
 *
 * @tsl
 * @function
 * @param {Node<mat2|mat3|mat4>} x - The parameter.
 * @returns {Node}
 */
const transpose = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.TRANSPOSE ).setParameterLength( 1 );

/**
 * Returns the determinant of a matrix.
 *
 * @tsl
 * @function
 * @param {Node<mat2|mat3|mat4>} x - The parameter.
 * @returns {Node<float>}
 */
const determinant = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.DETERMINANT ).setParameterLength( 1 );

/**
 * Returns the inverse of a matrix.
 *
 * @tsl
 * @function
 * @param {Node<mat2|mat3|mat4>} x - The parameter.
 * @returns {Node<mat2|mat3|mat4>}
 */
const inverse = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.INVERSE ).setParameterLength( 1 );

// 2 inputs

/**
 * Returns the least of the given values.
 *
 * @tsl
 * @function
 * @param {...(Node | number)} values - The values to compare.
 * @returns {Node}
 */
const min = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.MIN ).setParameterLength( 2, Infinity );

/**
 * Returns the greatest of the given values.
 *
 * @tsl
 * @function
 * @param {...(Node | number)} values - The values to compare.
 * @returns {Node}
 */
const max = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.MAX ).setParameterLength( 2, Infinity );

/**
 * Generate a step function by comparing two values.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The y parameter.
 * @param {Node | number} y - The x parameter.
 * @returns {Node}
 */
const step = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.STEP ).setParameterLength( 2 );

/**
 * Calculates the reflection direction for an incident vector.
 *
 * @tsl
 * @function
 * @param {Node<vec2|vec3|vec4>} I - The incident vector.
 * @param {Node<vec2|vec3|vec4>} N - The normal vector.
 * @returns {Node<vec2|vec3|vec4>}
 */
const reflect = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.REFLECT ).setParameterLength( 2 );

/**
 * Calculates the distance between two points.
 *
 * @tsl
 * @function
 * @param {Node<vec2|vec3|vec4>} x - The first point.
 * @param {Node<vec2|vec3|vec4>} y - The second point.
 * @returns {Node<float>}
 */
const distance = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.DISTANCE ).setParameterLength( 2 );

/**
 * Calculates the absolute difference between two values.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The first parameter.
 * @param {Node | number} y - The second parameter.
 * @returns {Node}
 */
const difference = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.DIFFERENCE ).setParameterLength( 2 );

/**
 * Calculates the dot product of two vectors.
 *
 * @tsl
 * @function
 * @param {Node<vec2|vec3|vec4>} x - The first vector.
 * @param {Node<vec2|vec3|vec4>} y - The second vector.
 * @returns {Node<float>}
 */
const dot = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.DOT ).setParameterLength( 2 );

/**
 * Calculates the cross product of two vectors.
 *
 * @tsl
 * @function
 * @param {Node<vec2|vec3>} x - The first vector.
 * @param {Node<vec2|vec3>} y - The second vector.
 * @returns {Node<float|vec3>}
 */
const cross = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.CROSS ).setParameterLength( 2 );

/**
 * Return the value of the first parameter raised to the power of the second one.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The first parameter.
 * @param {Node | number} y - The second parameter.
 * @returns {Node}
 */
const pow = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.POW ).setParameterLength( 2 );

/**
 * Returns the square of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The first parameter.
 * @returns {Node}
 */
const pow2 = ( x ) => mul( x, x );

/**
 * Returns the cube of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The first parameter.
 * @returns {Node}
 */
const pow3 = ( x ) => mul( x, x, x );

/**
 * Returns the fourth power of the parameter.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The first parameter.
 * @returns {Node}
 */
const pow4 = ( x ) => mul( x, x, x, x );

/**
 * Transforms the direction of a vector by a matrix and then normalizes the result.
 *
 * @tsl
 * @function
 * @param {Node<vec2|vec3|vec4>} direction - The direction vector.
 * @param {Node<mat2|mat3|mat4>} matrix - The transformation matrix.
 * @returns {Node}
 */
const transformDirection = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.TRANSFORM_DIRECTION ).setParameterLength( 2 );

/**
 * Transforms a normal vector by the view matrix and then normalizes the result.
 *
 * The upper-left 3x3 of the view matrix is assumed to be orthonormal, so the
 * normal can be transformed directly without involving the normal matrix.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} normal - The normal vector, given in world space.
 * @param {Node<mat3|mat4>} viewMatrix - The view matrix.
 * @returns {Node<vec3>} The normal vector in view space.
 */
const transformNormalByViewMatrix = ( normal, viewMatrix ) => normalize( mul( viewMatrix, vec4( vec3( normal ), 0.0 ) ).xyz );

/**
 * Transforms a normal vector by the inverse of the view matrix and then normalizes the result.
 *
 * The upper-left 3x3 of the view matrix is assumed to be orthonormal, so post-multiplying
 * by the view matrix is equivalent to pre-multiplying by its inverse.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} normal - The normal vector, given in view space.
 * @param {Node<mat3|mat4>} viewMatrix - The view matrix.
 * @returns {Node<vec3>} The normal vector in world space.
 */
const transformNormalByInverseViewMatrix = ( normal, viewMatrix ) => normalize( vec4( vec3( normal ), 0.0 ).mul( viewMatrix ).xyz );

/**
 * Returns the cube root of a number.
 *
 * @tsl
 * @function
 * @param {Node | number} a - The first parameter.
 * @returns {Node}
 */
const cbrt = ( a ) => mul( sign( a ), pow( abs( a ), 1.0 / 3.0 ) );

/**
 * Calculate the squared length of a vector.
 *
 * @tsl
 * @function
 * @param {Node<vec2|vec3|vec4>} a - The vector.
 * @returns {Node<float>}
 */
const lengthSq = ( a ) => dot( a, a );

/**
 * Linearly interpolates between two values.
 *
 * @tsl
 * @function
 * @param {Node | number} a - The first parameter.
 * @param {Node | number} b - The second parameter.
 * @param {Node | number} t - The interpolation value.
 * @returns {Node}
 */
const mix = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.MIX ).setParameterLength( 3 );

/**
 * Constrains a value to lie between two further values.
 *
 * @tsl
 * @function
 * @param {Node | number} value - The value to constrain.
 * @param {Node | number} [low=0] - The lower bound.
 * @param {Node | number} [high=1] - The upper bound.
 * @returns {Node}
 */
const clamp = ( value, low = 0, high = 1 ) => new MathNode( MathNode.CLAMP, nodeObject( value ), nodeObject( low ), nodeObject( high ) );

/**
 * Constrains a value between `0` and `1`.
 *
 * @tsl
 * @function
 * @param {Node | number} value - The value to constrain.
 * @returns {Node}
 */
const saturate = ( value ) => clamp( value );

/**
 * Calculates the refraction direction for an incident vector.
 *
 * @tsl
 * @function
 * @param {Node<vec2|vec3|vec4>} I - The incident vector.
 * @param {Node<vec2|vec3|vec4>} N - The normal vector.
 * @param {Node<float>} eta - The ratio of indices of refraction.
 * @returns {Node<vec2|vec3|vec4>}
 */
const refract = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.REFRACT ).setParameterLength( 3 );

/**
 * Performs a Hermite interpolation between two values.
 *
 * @tsl
 * @function
 * @param {Node | number} low - The value of the lower edge of the Hermite function.
 * @param {Node | number} high - The value of the upper edge of the Hermite function.
 * @param {Node | number} x - The source value for interpolation.
 * @returns {Node}
 */
const smoothstep = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.SMOOTHSTEP ).setParameterLength( 3 );

/**
 * Returns a vector pointing in the same direction as another.
 *
 * @tsl
 * @function
 * @param {Node<vec2|vec3|vec4>} N - The vector to orient.
 * @param {Node<vec2|vec3|vec4>} I - The incident vector.
 * @param {Node<vec2|vec3|vec4>} Nref - The reference vector.
 * @returns {Node<vec2|vec3|vec4>}
 */
const faceForward = /*@__PURE__*/ nodeProxyAssignable( MathNode, MathNode.FACEFORWARD ).setParameterLength( 3 );

/**
 * Returns a random value for the given uv.
 *
 * @tsl
 * @function
 * @param {Node<vec2>} uv - The uv node.
 * @returns {Node<float>}
 */
const rand = /*@__PURE__*/ Fn( ( [ uv ] ) => {

	const a = 12.9898, b = 78.233, c = 43758.5453;
	const dt = dot( uv.xy, vec2( a, b ) ), sn = mod( dt, PI );

	return fract( sin( sn ).mul( c ) );

}, { uv: 'vec2', return: 'float' } );

/**
 * Alias for `mix()` with a different parameter order.
 *
 * @tsl
 * @function
 * @param {Node | number} t - The interpolation value.
 * @param {Node | number} e1 - The first parameter.
 * @param {Node | number} e2 - The second parameter.
 * @returns {Node}
 */
const mixElement = ( t, e1, e2 ) => mix( e1, e2, t );

/**
 * Alias for `smoothstep()` with a different parameter order.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The source value for interpolation.
 * @param {Node | number} low - The value of the lower edge of the Hermite function.
 * @param {Node | number} high - The value of the upper edge of the Hermite function.
 * @returns {Node}
 */
const smoothstepElement = ( x, low, high ) => smoothstep( low, high, x );

/**
 * Alias for `step()` with a different parameter order.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The source value for interpolation.
 * @param {Node | number} edge - The edge value.
 * @returns {Node}
 */
const stepElement = ( x, edge ) => step( edge, x );

// GLSL alias function

const faceforward = faceForward;
const inversesqrt = inverseSqrt;

// Method chaining

addMethodChaining( 'all', all );
addMethodChaining( 'any', any );

addMethodChaining( 'radians', radians );
addMethodChaining( 'degrees', degrees );
addMethodChaining( 'exp', exp );
addMethodChaining( 'exp2', exp2 );
addMethodChaining( 'log', log );
addMethodChaining( 'log2', log2 );
addMethodChaining( 'sqrt', sqrt );
addMethodChaining( 'inverseSqrt', inverseSqrt );
addMethodChaining( 'floor', floor );
addMethodChaining( 'ceil', ceil );
addMethodChaining( 'normalize', normalize );
addMethodChaining( 'fract', fract );
addMethodChaining( 'sin', sin );
addMethodChaining( 'sinh', sinh );
addMethodChaining( 'cos', cos );
addMethodChaining( 'cosh', cosh );
addMethodChaining( 'tan', tan );
addMethodChaining( 'tanh', tanh );
addMethodChaining( 'asin', asin );
addMethodChaining( 'asinh', asinh );
addMethodChaining( 'acos', acos );
addMethodChaining( 'acosh', acosh );
addMethodChaining( 'atan', atan );
addMethodChaining( 'atanh', atanh );
addMethodChaining( 'abs', abs );
addMethodChaining( 'sign', sign );
addMethodChaining( 'length', length );
addMethodChaining( 'lengthSq', lengthSq );
addMethodChaining( 'negate', negate );
addMethodChaining( 'oneMinus', oneMinus );
addMethodChaining( 'dFdx', dFdx );
addMethodChaining( 'dFdy', dFdy );
addMethodChaining( 'round', round );
addMethodChaining( 'reciprocal', reciprocal );
addMethodChaining( 'trunc', trunc );
addMethodChaining( 'fwidth', fwidth );
addMethodChaining( 'min', min );
addMethodChaining( 'max', max );
addMethodChaining( 'step', stepElement );
addMethodChaining( 'reflect', reflect );
addMethodChaining( 'distance', distance );
addMethodChaining( 'dot', dot );
addMethodChaining( 'cross', cross );
addMethodChaining( 'pow', pow );
addMethodChaining( 'pow2', pow2 );
addMethodChaining( 'pow3', pow3 );
addMethodChaining( 'pow4', pow4 );
addMethodChaining( 'transformDirection', transformDirection );
addMethodChaining( 'transformNormalByViewMatrix', transformNormalByViewMatrix );
addMethodChaining( 'transformNormalByInverseViewMatrix', transformNormalByInverseViewMatrix );
addMethodChaining( 'mix', mixElement );
addMethodChaining( 'clamp', clamp );
addMethodChaining( 'refract', refract );
addMethodChaining( 'smoothstep', smoothstepElement );
addMethodChaining( 'faceForward', faceForward );
addMethodChaining( 'difference', difference );
addMethodChaining( 'saturate', saturate );
addMethodChaining( 'cbrt', cbrt );
addMethodChaining( 'transpose', transpose );
addMethodChaining( 'determinant', determinant );
addMethodChaining( 'inverse', inverse );
addMethodChaining( 'rand', rand );

/**
 * Custom error class for node-related errors, including stack trace information.
 */
class NodeError extends Error {

	constructor( message, stackTrace = null ) {

		super( message );

		/**
		 * The name of the error.
		 *
		 * @type {string}
		 */
		this.name = 'NodeError';

		/**
		 * The stack trace associated with the error.
		 *
		 * @type {?StackTrace}
		 */
		this.stackTrace = stackTrace;

	}

}

/**
 * Represents a logical `if/else` statement. Can be used as an alternative
 * to the `If()`/`Else()` syntax.
 *
 * The `select()` method is called in a chaining fashion on a condition. The parameter nodes of `select()`
 * determine the outcome of the entire statement.
 *
 * ```js
 * velocity = position.greaterThanEqual( limit ).select( velocity.negate(), velocity );
 * ```
 *
 * When the condition is itself a vector (e.g. the `bvec4` produced by
 * `someVec4.greaterThanEqual( someOtherVec4 )`), `select()` resolves
 * per-component - each output lane picks independently based on its own
 * condition component, the same way WGSL's native `select()` and GLSL's
 * `mix( x, y, bvecN )` do - rather than picking one branch for the whole
 * vector. The condition and values are converted to the largest vector width,
 * with the condition converted to boolean components. Scalar values are broadcast.
 *
 * ```js
 * // per-component: each channel picks independently
 * const clamped = value.greaterThan( vec3( 1.0 ) ).select( vec3( 1.0 ), value );
 * ```
 *
 * @augments Node
 */
class ConditionalNode extends Node {

	static get type() {

		return 'ConditionalNode';

	}

	/**
	 * Constructs a new conditional node.
	 *
	 * @param {Node} condNode - The node that defines the condition.
	 * @param {Node} ifNode - The node that is evaluate when the condition ends up `true`.
	 * @param {?Node} [elseNode=null] - The node that is evaluate when the condition ends up `false`.
	 */
	constructor( condNode, ifNode, elseNode = null ) {

		super();

		/**
		 * The node that defines the condition.
		 *
		 * @type {Node}
		 */
		this.condNode = condNode;

		/**
		 * The node that is evaluate when the condition ends up `true`.
		 *
		 * @type {Node}
		 */
		this.ifNode = ifNode;

		/**
		 * The node that is evaluate when the condition ends up `false`.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.elseNode = elseNode;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * This method is overwritten since the node type is inferred from the if/else
	 * nodes.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		const { condNode, ifNode, elseNode } = builder.getNodeProperties( this );

		if ( ifNode === undefined ) {

			// fallback setup

			builder.flowBuildStage( this, 'setup' );

			return this.getNodeType( builder );

		}

		let type = ifNode.getNodeType( builder );

		if ( elseNode !== null ) {

			const elseType = elseNode.getNodeType( builder );

			if ( builder.getTypeLength( elseType ) > builder.getTypeLength( type ) ) {

				type = elseType;

			}

		}

		const condLength = builder.getTypeLength( condNode.getNodeType( builder ) );

		if ( condLength > 1 && ! builder.isReference( type ) && ( builder.getTypeLength( type ) === 1 || builder.isVector( builder.getVectorType( type ) ) ) ) {

			type = builder.getTypeFromLength( Math.max( condLength, builder.getTypeLength( type ) ), builder.getComponentType( type ) );

		}

		return type;

	}

	setup( builder ) {

		const { condNode, ifNode, elseNode } = this;

		const properties = builder.getNodeProperties( this );
		properties.condNode = condNode;
		properties.ifNode = ifNode;
		properties.elseNode = elseNode;

	}

	generate( builder, output ) {

		const type = this.getNodeType( builder );

		const nodeData = builder.getDataFromNode( this );

		if ( nodeData.propertyName !== undefined ) {

			return builder.format( nodeData.propertyName, type, output );

		}

		const { condNode, ifNode, elseNode } = builder.getNodeProperties( this );

		const functionNode = builder.currentFunctionNode;
		const needsOutput = output !== 'void';
		const nodeProperty = needsOutput ? property( type ).build( builder ) : '';

		nodeData.propertyName = nodeProperty;

		// A vector condition selects per-component - see getVectorSelect().
		const condType = condNode.getNodeType( builder );
		const condLength = builder.getTypeLength( condType );

		if ( condLength > 1 ) {

			const vectorType = builder.getVectorType( type );

			if ( builder.isReference( type ) || ! builder.isVector( vectorType ) ) {

				throw new NodeError( `TSL: select() with a vector condition ("${ condType }") requires scalar or vector values, received "${ type }".`, this.stackTrace );

			}

			// No "else": unselected lanes fall back to the type's zero value.
			let elseSnippet;

			if ( elseNode !== null ) {

				elseSnippet = elseNode.build( builder, type );

			} else {

				elseSnippet = builder.generateConst( type );

			}

			const boolType = builder.changeComponentType( type, 'bool' );
			const condSnippet = condNode.build( builder, boolType );
			const ifSnippet = ifNode.build( builder, type );

			const mathSnippet = builder.getVectorSelect( condSnippet, ifSnippet, elseSnippet, type );

			if ( ! needsOutput ) return '';

			builder.addFlowCode( `\n${ builder.tab }${ nodeProperty } = ${ mathSnippet };\n\n` );

			return builder.format( nodeProperty, type, output );

		}

		const nodeSnippet = condNode.build( builder, 'bool' );
		const isUniformFlow = builder.context.uniformFlow;

		if ( isUniformFlow && elseNode !== null ) {

			const ifSnippet = ifNode.build( builder, type );
			const elseSnippet = elseNode.build( builder, type );

			const mathSnippet = builder.getTernary( nodeSnippet, ifSnippet, elseSnippet );

			// TODO: If node property already exists return something else

			return builder.format( mathSnippet, type, output );

		}

		builder.addFlowCode( `\n${ builder.tab }if ( ${ nodeSnippet } ) {\n\n` ).addFlowTab();

		// An immutable condition, i.e. a constant, is the guard of the blocks: blocks of the same parent and guard run together.
		const condData = builder.getDataFromNode( condNode );
		const guard = condData.propertyName === nodeSnippet && condData.assign !== true ? nodeSnippet : undefined;

		const flowBlock = builder.flowBlock;

		builder.flowBlock = { parent: flowBlock, guard };

		let ifSnippet = ifNode.build( builder, type );

		builder.flowBlock = flowBlock;

		if ( ifSnippet ) {

			if ( needsOutput ) {

				ifSnippet = nodeProperty + ' = ' + ifSnippet + ';';

			} else {

				ifSnippet = 'return ' + ifSnippet + ';';

				if ( functionNode === null ) {

					warn( 'TSL: Return statement used in an inline \'Fn()\'. Define a layout struct to allow return values.', this.stackTrace );

					ifSnippet = '// ' + ifSnippet;

				}

			}

		}

		builder.removeFlowTab().addFlowCode( builder.tab + '\t' + ifSnippet + '\n\n' + builder.tab + '}' );

		if ( elseNode !== null ) {

			builder.addFlowCode( ' else {\n\n' ).addFlowTab();

			builder.flowBlock = { parent: flowBlock, guard: guard !== undefined ? '!' + guard : undefined };

			let elseSnippet = elseNode.build( builder, type );

			builder.flowBlock = flowBlock;

			if ( elseSnippet ) {

				if ( needsOutput ) {

					elseSnippet = nodeProperty + ' = ' + elseSnippet + ';';

				} else {

					elseSnippet = 'return ' + elseSnippet + ';';

					if ( functionNode === null ) {

						warn( 'TSL: Return statement used in an inline \'Fn()\'. Define a layout struct to allow return values.', this.stackTrace );

						elseSnippet = '// ' + elseSnippet;

					}

				}

			}

			builder.removeFlowTab().addFlowCode( builder.tab + '\t' + elseSnippet + '\n\n' + builder.tab + '}\n\n' );

		} else {

			builder.addFlowCode( '\n\n' );

		}

		return builder.format( nodeProperty, type, output );

	}

}

/**
 * TSL function for creating a conditional node.
 *
 * @tsl
 * @function
 * @param {Node} condNode - The node that defines the condition.
 * @param {Node} ifNode - The node that is evaluate when the condition ends up `true`.
 * @param {?Node} [elseNode=null] - The node that is evaluate when the condition ends up `false`.
 * @returns {ConditionalNode}
 */
const select = /*@__PURE__*/ nodeProxy( ConditionalNode ).setParameterLength( 2, 3 );

addMethodChaining( 'select', select );

/**
 * This node can be used as a context management component for another node.
 * {@link NodeBuilder} performs its node building process in a specific context and
 * this node allows the modify the context. A typical use case is to overwrite `getUV()` e.g.:
 *
 * ```js
 *node.context( { getUV: () => customCoord } );
 *\// or
 *material.contextNode = context( { getUV: () => customCoord } );
 *\// or
 *renderer.contextNode = context( { getUV: () => customCoord } );
 *\// or
 *scenePass.contextNode = context( { getUV: () => customCoord } );
 *```
 * @augments Node
 */
class ContextNode extends Node {

	static get type() {

		return 'ContextNode';

	}

	/**
	 * Constructs a new context node.
	 *
	 * @param {Node} node - The node whose context should be modified.
	 * @param {Object} [value={}] - The modified context data.
	 */
	constructor( node = null, value = {} ) {

		super();

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isContextNode = true;

		/**
		 * The node whose context should be modified.
		 *
		 * @type {Node}
		 */
		this.node = node;

		/**
		 * The modified context data.
		 *
		 * @type {Object}
		 * @default {}
		 */
		this.value = value;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * This method is overwritten to ensure it returns the reference to {@link ContextNode#node}.
	 *
	 * @return {Node} A reference to {@link ContextNode#node}.
	 */
	getScope() {

		return this.node.getScope();

	}

	/**
	 * This method is overwritten to ensure it returns the type of {@link ContextNode#node}.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		return this.node.getNodeType( builder );

	}

	/**
	 * Gathers the context data from all parent context nodes.
	 *
	 * @return {Object} The gathered context data.
	 */
	getFlowContextData() {

		let data = {};

		this.traverse( ( node ) => {

			if ( node.isContextNode === true ) {

				data = mergeContext( data, node.value );

			}

		} );

		return data;

	}

	/**
	 * This method is overwritten to ensure it returns the member type of {@link ContextNode#node}.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} name - The member name.
	 * @returns {string} The member type.
	 */
	getMemberType( builder, name ) {

		return this.node.getMemberType( builder, name );

	}

	analyze( builder ) {

		const usageCount = builder.increaseUsage( this );

		if ( usageCount === 1 ) {

			const previousContext = builder.addContext( this.value );

			this.node.build( builder, this );

			builder.setContext( previousContext );

		}

	}

	setup( builder ) {

		const previousContext = builder.addContext( this.value );

		this.node.build( builder );

		builder.setContext( previousContext );

	}

	generate( builder, output ) {

		const previousContext = builder.addContext( this.value );

		const snippet = this.node.build( builder, output );

		builder.setContext( previousContext );

		return snippet;

	}

}

/**
 * TSL function for creating a context node.
 *
 * @tsl
 * @function
 * @param {Node|Object} [nodeOrValue={}] - The node whose context should be modified or the modified context data.
 * @param {Object} [value={}] - The modified context data.
 * @returns {ContextNode}
 */
const context = ( nodeOrValue = null, value = {} ) => {

	let node = nodeOrValue;

	if ( node === null || node.isNode !== true ) {

		value = node || value;
		node = null;

	}

	return new ContextNode( node, value );

};

/**
 * TSL function for defining a uniformFlow context value for a given node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node whose dependencies should all execute within a uniform control-flow path.
 * @returns {ContextNode}
 */
const uniformFlow = ( node ) => context( node, { uniformFlow: true } );

/**
 * TSL function for defining a name for the context value for a given node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node whose context should be modified.
 * @param {string} name - The name to set.
 * @returns {ContextNode}
 */
const setName = ( node, name ) => context( node, { nodeName: name } );

/**
 * TSL function for defining a built-in shadow context for a given node.
 *
 * @tsl
 * @function
 * @param {ShadowNode} shadowNode - The shadow node representing the light's shadow.
 * @param {Light} light - The light associated with the shadow.
 * @param {Node} [node=null] - The node whose context should be modified.
 * @returns {ContextNode}
 */
function builtinShadowContext( shadowNode, light, node = null ) {

	return context( node, {

		getShadow: ( { light: shadowLight, shadowColorNode } ) => {

			if ( light === shadowLight ) {

				return shadowColorNode.mul( shadowNode );

			}

			return shadowColorNode;

		}

	} );

}

/**
 * TSL function for defining a built-in ambient occlusion context for a given node.
 *
 * @tsl
 * @function
 * @param {Node} aoNode - The ambient occlusion value node to apply.
 * @param {Node} [node=null] - The node whose context should be modified.
 * @returns {ContextNode}
 */
function builtinAOContext( aoNode, node = null ) {

	return context( node, {

		getAO: ( inputNode, { material } ) => {

			if ( material.transparent === true ) return inputNode;

			return inputNode !== null ? inputNode.mul( aoNode ) : aoNode;

		}

	} );

}

/**
 * TSL function for defining a built-in global illumination context for a given node. The AO node
 * modulates the indirect lighting of the materials, the GI node is added to their irradiance
 * without being modulated by the AO since it already accounts for occlusion.
 *
 * @tsl
 * @function
 * @param {Node<float>} aoNode - The ambient occlusion value node to apply.
 * @param {Node<vec3>} giNode - The indirect diffuse irradiance node to add.
 * @param {Node} [node=null] - The node whose context should be modified.
 * @returns {ContextNode}
 */
function builtinGIContext( aoNode, giNode, node = null ) {

	// the lighting models multiply the whole indirect diffuse term by the AO, which would occlude
	// the GI a second time. Dividing the GI by the AO cancels that out so only the environment
	// lighting is modulated.

	const ao = aoNode.max( 1e-3 );

	return context( node, {

		getAO: ( inputNode, { material } ) => {

			if ( material.transparent === true ) return inputNode;

			return inputNode !== null ? inputNode.mul( ao ) : ao;

		},

		getGI: ( inputNode, { material } ) => {

			if ( material.transparent === true ) return inputNode;

			const gi = giNode.div( ao );

			return inputNode !== null ? inputNode.add( gi ) : gi;

		}

	} );

}

/**
 * TSL function for defining a label context value for a given node.
 *
 * @tsl
 * @function
 * @deprecated
 * @param {Node} node - The node whose context should be modified.
 * @param {string} name - The name/label to set.
 * @returns {ContextNode}
 */
function label( node, name ) {

	warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.' ); // @deprecated r179

	return setName( node, name );

}

addMethodChaining( 'context', context );
addMethodChaining( 'label', label );
addMethodChaining( 'uniformFlow', uniformFlow );
addMethodChaining( 'setName', setName );
addMethodChaining( 'builtinShadowContext', ( node, shadowNode, light ) => builtinShadowContext( shadowNode, light, node ) );
addMethodChaining( 'builtinAOContext', ( node, aoValue ) => builtinAOContext( aoValue, node ) );
addMethodChaining( 'builtinGIContext', ( node, aoValue, giValue ) => builtinGIContext( aoValue, giValue, node ) );

/**
 * Class for representing shader variables as nodes. Variables are created from
 * existing nodes like the following:
 *
 * ```js
 * const depth = sampleDepth( uvNode ).toVar( 'depth' );
 * ```
 *
 * @augments Node
 */
class VarNode extends Node {

	static get type() {

		return 'VarNode';

	}

	/**
	 * Constructs a new variable node.
	 *
	 * @param {Node} node - The node for which a variable should be created.
	 * @param {?string} [name=null] - The name of the variable in the shader.
	 * @param {boolean} [readOnly=false] - The read-only flag.
	 */
	constructor( node, name = null, readOnly = false ) {

		super();

		/**
		 * The node for which a variable should be created.
		 *
		 * @type {Node}
		 */
		this.node = node;

		/**
		 * The name of the variable in the shader. If no name is defined,
		 * the node system auto-generates one.
		 *
		 * @type {?string}
		 * @default null
		 */
		this.name = name;

		/**
		 * `VarNode` sets this property to `true` by default.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isVarNode = true;

		/**
		 *
		 * The read-only flag.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.readOnly = readOnly;

		/**
		 *
		 * Add this flag to the node system to indicate that this node require parents.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.parents = true;

		/**
		 * Whether this node can be generated as an expression until it is assigned.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.assignable = false;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Sets the assignable flag for this node.
	 *
	 * An assignable node is generated as an expression until it is assigned,
	 * at which point it is declared as a variable.
	 *
	 * @param {boolean} value - The value to set for the assignable flag.
	 * @returns {VarNode} This node.
	 */
	setAssignable( value ) {

		this.assignable = value;

		return this;

	}

	/**
	 * Returns the assignable flag of this node.
	 *
	 * @return {boolean} The assignable flag.
	 */
	getAssignable() {

		return this.assignable;

	}

	getMemberType( builder, name ) {

		return this.node.getMemberType( builder, name );

	}

	getElementType( builder ) {

		return this.node.getElementType( builder );

	}

	generateNodeType( builder ) {

		return this.node.getNodeType( builder );

	}

	getArrayCount( builder ) {

		return this.node.getArrayCount( builder );

	}

	_declaresAssignable() {

		return false;

	}

	_isFunctionScope() {

		// see `generate()`, a read-only or local variable is declared where it is generated
		return this.readOnly === false && this.assignable === false;

	}

	build( builder, output = null ) {

		this._setupInspector( builder );

		const refNode = this.getShared( builder );

		if ( this !== refNode ) {

			return refNode.build( builder, output );

		}

		const data = builder.getDataFromNode( this );

		this._updateAssignable( builder, data, this.node );

		// An assignable node that is not assigned is just its value.
		if ( this.assignable && data.forceDeclaration !== true && data.assign !== true ) {

			return this.node.build( builder, output );

		}

		return super.build( builder, output );

	}

	generate( builder ) {

		const { node, name, readOnly } = this;

		const nodeType = this.getNodeType( builder );

		if ( nodeType == 'void' ) {

			if ( this.isAssignable( builder ) !== true ) {

				error( 'TSL: ".toVar()" can not be used with void type.', this.stackTrace );

			}

			const snippet = node.build( builder );

			return snippet;

		}

		const vectorType = builder.getVectorType( nodeType );
		const snippet = node.build( builder, vectorType );

		const nodeVar = builder.getVarFromNode( this, name, vectorType, undefined, readOnly, this.assignable );

		const propertyName = builder.getPropertyName( nodeVar );

		let declarationPrefix = propertyName;

		if ( nodeVar.readOnly ) {

			const count = node.getArrayCount( builder );

			declarationPrefix = builder.isDeterministic( node )
				? builder.generateConstStatement( nodeVar.type, propertyName, count )
				: builder.generateLetStatement( nodeVar.type, propertyName, count );

		} else if ( nodeVar.local ) {

			declarationPrefix = builder.generateVarStatement( nodeVar.type, propertyName, nodeVar.count );

		}

		builder.addLineFlowCode( `${ declarationPrefix } = ${ snippet }`, this );

		return propertyName;

	}

}

/**
 * TSL function for creating a var node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node for which a variable should be created.
 * @param {?string} name - The name of the variable in the shader.
 * @returns {VarNode}
 */
const createVar = /*@__PURE__*/ nodeProxy( VarNode );

/**
 * TSL function for creating a var node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node for which a variable should be created.
 * @param {?string} name - The name of the variable in the shader.
 * @returns {VarNode}
 */
const Var = ( node, name = null ) => createVar( node, name ).toStack();

/**
 * TSL function for creating a const node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node for which a constant should be created.
 * @param {?string} name - The name of the constant in the shader.
 * @returns {VarNode}
 */
const Const = ( node, name = null ) => createVar( node, name, true ).toStack();

// Method chaining

addMethodChaining( 'toVar', Var );
addMethodChaining( 'toConst', Const );

/**
 * This node is used to build a sub-build in the node system.
 *
 * @augments Node
 * @param {Node} node - The node to be built in the sub-build.
 * @param {string} name - The name of the sub-build.
 * @param {?string} [nodeType=null] - The type of the node, if known.
 */
class SubBuildNode extends Node {

	static get type() {

		return 'SubBuild';

	}

	constructor( node, name, nodeType = null ) {

		super( nodeType );

		/**
		 * The node to be built in the sub-build.
		 *
		 * @type {Node}
		 */
		this.node = node;

		/**
		 * The name of the sub-build.
		 *
		 * @type {string}
		 */
		this.name = name;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isSubBuildNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generateNodeType( builder ) {

		if ( this.nodeType !== null ) return this.nodeType;

		builder.addSubBuild( this.name );

		const nodeType = this.node.getNodeType( builder );

		builder.removeSubBuild();

		return nodeType;

	}

	build( builder, ...params ) {

		this._setupInspector( builder );

		builder.addSubBuild( this.name );

		const data = this.node.build( builder, ...params );

		builder.removeSubBuild();

		return data;

	}

}

/**
 * Creates a new sub-build node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node to be built in the sub-build.
 * @param {string} name - The name of the sub-build.
 * @param {?string} [type=null] - The type of the node, if known.
 * @returns {Node} A node object wrapping the SubBuildNode instance.
 */
const subBuild = ( node, name, type = null ) => new SubBuildNode( nodeObject( node ), name, type );

/**
 * Class for representing shader varyings as nodes. Varyings are create from
 * existing nodes like the following:
 *
 * ```js
 * const positionLocal = positionGeometry.toVarying( 'vPositionLocal' );
 * ```
 *
 * @augments Node
 */
class VaryingNode extends Node {

	static get type() {

		return 'VaryingNode';

	}

	/**
	 * Constructs a new varying node.
	 *
	 * @param {Node} node - The node for which a varying should be created.
	 * @param {?string} name - The name of the varying in the shader.
	 */
	constructor( node, name = null ) {

		super();

		/**
		 * The node for which a varying should be created.
		 *
		 * @type {Node}
		 */
		this.node = subBuild( node, 'VERTEX' );

		/**
		 * The name of the varying in the shader. If no name is defined,
		 * the node system auto-generates one.
		 *
		 * @type {?string}
		 * @default null
		 */
		this.name = name;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isVaryingNode = true;

		/**
		 * The interpolation type of the varying data.
		 *
		 * @type {?string}
		 * @default null
		 */
		this.interpolationType = null;

		/**
		 * The interpolation sampling type of varying data.
		 *
		 * @type {?string}
		 * @default null
		 */
		this.interpolationSampling = null;

		/**
		 * This flag is used for global cache.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Defines the interpolation type of the varying.
	 *
	 * @param {string} type - The interpolation type.
	 * @param {?string} sampling - The interpolation sampling type
	 * @return {VaryingNode} A reference to this node.
	 */
	setInterpolation( type, sampling = null ) {

		this.interpolationType = type;
		this.interpolationSampling = sampling;

		return this;

	}

	getHash( builder ) {

		return this.name || super.getHash( builder );

	}

	generateNodeType( builder ) {

		// VaryingNode is auto type

		return this.node.getNodeType( builder );

	}

	/**
	 * This method performs the setup of a varying node with the current node builder.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {NodeVarying} The node varying from the node builder.
	 */
	setupVarying( builder ) {

		const properties = builder.getNodeProperties( this );

		let varying = properties.varying;

		if ( varying === undefined ) {

			const name = this.name;
			const type = this.getNodeType( builder );
			const interpolationType = this.interpolationType;
			const interpolationSampling = this.interpolationSampling;

			properties.varying = varying = builder.getVaryingFromNode( this, name, type, interpolationType, interpolationSampling );
			properties.node = subBuild( this.node, 'VERTEX' );

		}

		// this property can be used to check if the varying can be optimized for a variable
		varying.needsInterpolation || ( varying.needsInterpolation = ( builder.shaderStage === 'fragment' ) );

		return varying;

	}

	setup( builder ) {

		// the context can evaluate varyings directly in the fragment stage, e.g. if the surface is reconstructed per fragment.
		// indices and attributes create their own varyings in the fragment stage, so their varyings are kept unless the
		// context provides the attribute

		if ( builder.context.inlineVaryings === true && builder.shaderStage === 'fragment' ) {

			let node = this.node;

			while ( node.isSubBuildNode === true ) node = node.node;

			if ( node.isIndexNode !== true && ( node.isAttributeNode !== true || node.getContextAttribute( builder ) ) ) return this.node;

		}

		this.setupVarying( builder );

		builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node );

	}

	analyze( builder ) {

		// TODO: Node.build() could pass through the output node of setup() directly, so overrides of analyze() and generate() don't need to handle it twice

		if ( builder.getNodeProperties( this ).outputNode ) return super.analyze( builder );

		this.setupVarying( builder );

		builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node );

	}

	generate( builder ) {

		const { outputNode } = builder.getNodeProperties( this );

		if ( outputNode ) return outputNode.build( builder, this.getNodeType( builder ) );

		const propertyKey = builder.getSubBuildProperty( 'property', builder.currentStack );
		const properties = builder.getNodeProperties( this );
		const varying = this.setupVarying( builder );

		// The vertex assignment is emitted once per block, from the fragment stage it is emitted outside of any block.
		const flowBlock = builder.shaderStage === NodeShaderStage.VERTEX ? builder.flowBlock : null;

		if ( properties[ propertyKey ] !== flowBlock ) {

			const type = this.getNodeType( builder );
			const propertyName = builder.getPropertyName( varying, NodeShaderStage.VERTEX );

			if ( builder.shaderStage === NodeShaderStage.VERTEX ) {

				const snippet = properties.node.build( builder, type );

				builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this );

			} else {

				// force node run in vertex stage
				builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, properties.node, type, propertyName );

			}

			properties[ propertyKey ] = flowBlock;

		}

		return builder.getPropertyName( varying );

	}

}

/**
 * TSL function for creating a varying node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node for which a varying should be created.
 * @param {?string} name - The name of the varying in the shader.
 * @returns {VaryingNode}
 */
const varying = /*@__PURE__*/ nodeProxy( VaryingNode ).setParameterLength( 1, 2 );

/**
 * Computes a node in the vertex stage.
 *
 * @tsl
 * @function
 * @param {Node} node - The node which should be executed in the vertex stage.
 * @returns {VaryingNode}
 */
const vertexStage = ( node ) => varying( node );

addMethodChaining( 'toVarying', varying );
addMethodChaining( 'toVertexStage', vertexStage );

/**
 * Converts the given color value from sRGB to linear-sRGB color space.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The sRGB color.
 * @return {Node<vec3>} The linear-sRGB color.
 */
const sRGBTransferEOTF = /*@__PURE__*/ Fn( ( [ color ] ) => {

	const a = color.mul( 0.9478672986 ).add( 0.0521327014 ).pow( 2.4 );
	const b = color.mul( 0.0773993808 );
	const factor = color.lessThanEqual( 0.04045 );

	const rgbResult = mix( a, b, factor );

	return rgbResult;

}, { color: 'vec3', return: 'vec3' } );

/**
 * Converts the given color value from linear-sRGB to sRGB color space.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The linear-sRGB color.
 * @return {Node<vec3>} The sRGB color.
 */
const sRGBTransferOETF = /*@__PURE__*/ Fn( ( [ color ] ) => {

	const a = color.pow( 0.41666 ).mul( 1.055 ).sub( 0.055 );
	const b = color.mul( 12.92 );
	const factor = color.lessThanEqual( 0.0031308 );

	const rgbResult = mix( a, b, factor );

	return rgbResult;

}, { color: 'vec3', return: 'vec3' } );

const WORKING_COLOR_SPACE = 'WorkingColorSpace';
const OUTPUT_COLOR_SPACE = 'OutputColorSpace';

/**
 * This node represents a color space conversion. Meaning it converts
 * a color value from a source to a target color space.
 *
 * @augments Node
 */
class ColorSpaceNode extends Node {

	static get type() {

		return 'ColorSpaceNode';

	}

	/**
	 * Constructs a new color space node.
	 *
	 * @param {Node} colorNode - Represents the color to convert.
	 * @param {string} source - The source color space.
	 * @param {string} target - The target color space.
	 */
	constructor( colorNode, source, target ) {

		super( 'vec4' );

		/**
		 * Represents the color to convert.
		 *
		 * @type {Node}
		 */
		this.colorNode = colorNode;

		/**
		 * The source color space.
		 *
		 * @type {string}
		 */
		this.source = source;

		/**
		 * The target color space.
		 *
		 * @type {string}
		 */
		this.target = target;

	}

	/**
	 * This method resolves the constants `WORKING_COLOR_SPACE` and
	 * `OUTPUT_COLOR_SPACE` based on the current configuration of the
	 * color management and renderer.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} colorSpace - The color space to resolve.
	 * @return {string} The resolved color space.
	 */
	resolveColorSpace( builder, colorSpace ) {

		if ( colorSpace === WORKING_COLOR_SPACE ) {

			return ColorManagement.workingColorSpace;

		} else if ( colorSpace === OUTPUT_COLOR_SPACE ) {

			return builder.context.outputColorSpace || builder.renderer.outputColorSpace;

		}

		return colorSpace;

	}

	setup( builder ) {

		const { colorNode } = this;

		const source = this.resolveColorSpace( builder, this.source );
		const target = this.resolveColorSpace( builder, this.target );

		let outputNode = colorNode;

		if ( ColorManagement.enabled === false || source === target || ! source || ! target ) {

			return outputNode;

		}

		if ( ColorManagement.getTransfer( source ) === SRGBTransfer ) {

			outputNode = vec4( sRGBTransferEOTF( outputNode.rgb ), outputNode.a );

		}

		if ( ColorManagement.getPrimaries( source ) !== ColorManagement.getPrimaries( target ) ) {

			outputNode = vec4(
				mat3( ColorManagement._getMatrix( new Matrix3(), source, target ) ).mul( outputNode.rgb ),
				outputNode.a
			);

		}

		if ( ColorManagement.getTransfer( target ) === SRGBTransfer ) {

			outputNode = vec4( sRGBTransferOETF( outputNode.rgb ), outputNode.a );

		}

		return outputNode;

	}

}

/**
 * TSL function for converting a given color node from the current working color space to the given color space.
 *
 * @tsl
 * @function
 * @param {Node} node - Represents the node to convert.
 * @param {string} targetColorSpace - The target color space.
 * @returns {ColorSpaceNode}
 */
const workingToColorSpace = ( node, targetColorSpace ) => new ColorSpaceNode( nodeObject( node ), WORKING_COLOR_SPACE, targetColorSpace );

/**
 * TSL function for converting a given color node from the given color space to the current working color space.
 *
 * @tsl
 * @function
 * @param {Node} node - Represents the node to convert.
 * @param {string} sourceColorSpace - The source color space.
 * @returns {ColorSpaceNode}
 */
const colorSpaceToWorking = ( node, sourceColorSpace ) => new ColorSpaceNode( nodeObject( node ), sourceColorSpace, WORKING_COLOR_SPACE );

/**
 * TSL function for converting a given color node from one color space to another one.
 *
 * @tsl
 * @function
 * @param {Node} node - Represents the node to convert.
 * @param {string} sourceColorSpace - The source color space.
 * @param {string} targetColorSpace - The target color space.
 * @returns {ColorSpaceNode}
 */
const convertColorSpace = ( node, sourceColorSpace, targetColorSpace ) => new ColorSpaceNode( nodeObject( node ), sourceColorSpace, targetColorSpace );

addMethodChaining( 'workingToColorSpace', workingToColorSpace );
addMethodChaining( 'colorSpaceToWorking', colorSpaceToWorking );

/**
 * This class is only relevant if the referenced property is array-like.
 * In this case, `ReferenceElementNode` allows to refer to a specific
 * element inside the data structure via an index.
 *
 * @augments ArrayElementNode
 */
class ReferenceElementNode extends ArrayElementNode {

	static get type() {

		return 'ReferenceElementNode';

	}

	/**
	 * Constructs a new reference element node.
	 *
	 * @param {(ReferenceBaseNode|ReferenceNode)} referenceNode - The reference node.
	 * @param {Node} indexNode - The index node that defines the element access.
	 */
	constructor( referenceNode, indexNode ) {

		super( referenceNode, indexNode );

		/**
		 * Similar to {@link ReferenceNode#reference}, an additional
		 * property references to the current node.
		 *
		 * @type {?(ReferenceBaseNode|ReferenceNode)}
		 * @default null
		 */
		this.referenceNode = referenceNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isReferenceElementNode = true;

	}

	/**
	 * This method is overwritten since the node type is inferred from
	 * the uniform type of the reference node.
	 *
	 * @return {string} The node type.
	 */
	generateNodeType() {

		return this.referenceNode.uniformType;

	}

	generate( builder ) {

		const snippet = super.generate( builder );
		const arrayType = this.referenceNode.getNodeType( builder );
		const elementType = this.getNodeType( builder );

		return builder.format( snippet, arrayType, elementType );

	}

}

// TODO: Avoid duplicated code and use only ReferenceBaseNode or ReferenceNode

/**
 * Base class for nodes which establishes a reference to a property of another object.
 * In this way, the value of the node is automatically linked to the value of
 * referenced object. Reference nodes internally represent the linked value
 * as a uniform.
 *
 * @augments Node
 */
class ReferenceBaseNode extends Node {

	static get type() {

		return 'ReferenceBaseNode';

	}

	/**
	 * Constructs a new reference base node.
	 *
	 * @param {string} property - The name of the property the node refers to.
	 * @param {string} uniformType - The uniform type that should be used to represent the property value.
	 * @param {?Object} [object=null] - The object the property belongs to.
	 * @param {?number} [count=null] - When the linked property is an array-like, this parameter defines its length.
	 */
	constructor( property, uniformType, object = null, count = null ) {

		super();

		/**
		 * The name of the property the node refers to.
		 *
		 * @type {string}
		 */
		this.property = property;

		/**
		 * The uniform type that should be used to represent the property value.
		 *
		 * @type {string}
		 */
		this.uniformType = uniformType;

		/**
		 * The object the property belongs to.
		 *
		 * @type {?Object}
		 * @default null
		 */
		this.object = object;

		/**
		 * When the linked property is an array, this parameter defines its length.
		 *
		 * @type {?number}
		 * @default null
		 */
		this.count = count;

		/**
		 * The property name might have dots so nested properties can be referred.
		 * The hierarchy of the names is stored inside this array.
		 *
		 * @type {Array<string>}
		 */
		this.properties = property.split( '.' );

		/**
		 * Points to the current referred object. This property exists next to {@link ReferenceNode#object}
		 * since the final reference might be updated from calling code.
		 *
		 * @type {?Object}
		 * @default null
		 */
		this.reference = object;

		/**
		 * The uniform node that holds the value of the reference node.
		 *
		 * @type {UniformNode}
		 * @default null
		 */
		this.node = null;

		/**
		 * The uniform group of the internal uniform.
		 *
		 * @type {UniformGroupNode}
		 * @default null
		 */
		this.group = null;

		/**
		 * Overwritten since reference nodes are updated per object.
		 *
		 * @type {string}
		 * @default 'object'
		 */
		this.updateType = NodeUpdateType.OBJECT;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Sets the uniform group for this reference node.
	 *
	 * @param {UniformGroupNode} group - The uniform group to set.
	 * @return {ReferenceBaseNode} A reference to this node.
	 */
	setGroup( group ) {

		this.group = group;

		return this;

	}

	/**
	 * When the referred property is array-like, this method can be used
	 * to access elements via an index node.
	 *
	 * @param {IndexNode} indexNode - indexNode.
	 * @return {ReferenceElementNode} A reference to an element.
	 */
	element( indexNode ) {

		return new ReferenceElementNode( this, nodeObject( indexNode ) );

	}

	/**
	 * Sets the node type which automatically defines the internal
	 * uniform type.
	 *
	 * @param {string} uniformType - The type to set.
	 */
	setNodeType( uniformType ) {

		const node = uniform( null, uniformType );

		if ( this.group !== null ) {

			node.setGroup( this.group );

		}

		this.node = node;

	}

	/**
	 * This method is overwritten since the node type is inferred from
	 * the type of the reference node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		if ( this.node === null ) {

			this.updateReference( builder );
			this.updateValue();

		}

		return this.node.getNodeType( builder );

	}

	/**
	 * Returns the property value from the given referred object.
	 *
	 * @param {Object} [object=this.reference] - The object to retrieve the property value from.
	 * @return {any} The value.
	 */
	getValueFromReference( object = this.reference ) {

		const { properties } = this;

		let value = object[ properties[ 0 ] ];

		for ( let i = 1; i < properties.length; i ++ ) {

			value = value[ properties[ i ] ];

		}

		return value;

	}

	/**
	 * Allows to update the reference based on the given state. The state is only
	 * evaluated {@link ReferenceBaseNode#object} is not set.
	 *
	 * @param {(NodeFrame|NodeBuilder)} state - The current state.
	 * @return {Object} The updated reference.
	 */
	updateReference( state ) {

		this.reference = this.object !== null ? this.object : state.object;

		return this.reference;

	}

	/**
	 * The output of the reference node is the internal uniform node.
	 *
	 * @return {UniformNode} The output node.
	 */
	setup() {

		this.updateValue();

		return this.node;

	}

	/**
	 * Overwritten to update the internal uniform value.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	update( /*frame*/ ) {

		this.updateValue();

	}

	/**
	 * Retrieves the value from the referred object property and uses it
	 * to updated the internal uniform.
	 */
	updateValue() {

		if ( this.node === null ) this.setNodeType( this.uniformType );

		const value = this.getValueFromReference();

		if ( Array.isArray( value ) ) {

			this.node.array = value;

		} else {

			this.node.value = value;

		}

	}

}

/**
 * TSL function for creating a reference base node.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the property the node refers to.
 * @param {string} type - The uniform type that should be used to represent the property value.
 * @param {Object} object - The object the property belongs to.
 * @returns {ReferenceBaseNode}
 */
const reference$1 = ( name, type, object ) => new ReferenceBaseNode( name, type, object );

/**
 * This node is a special type of reference node which is intended
 * for linking renderer properties with node values.
 * ```js
 * const exposureNode = rendererReference( 'toneMappingExposure', 'float', renderer );
 * ```
 * When changing `renderer.toneMappingExposure`, the node value of `exposureNode` will
 * automatically be updated.
 *
 * @augments ReferenceBaseNode
 */
class RendererReferenceNode extends ReferenceBaseNode {

	static get type() {

		return 'RendererReferenceNode';

	}

	/**
	 * Constructs a new renderer reference node.
	 *
	 * @param {string} property - The name of the property the node refers to.
	 * @param {string} inputType - The uniform type that should be used to represent the property value.
	 * @param {?Renderer} [renderer=null] - The renderer the property belongs to. When no renderer is set,
	 * the node refers to the renderer of the current state.
	 */
	constructor( property, inputType, renderer = null ) {

		super( property, inputType, renderer );

		/**
		 * The renderer the property belongs to. When no renderer is set,
		 * the node refers to the renderer of the current state.
		 *
		 * @type {?Renderer}
		 * @default null
		 */
		this.renderer = renderer;

		this.setGroup( renderGroup );

	}

	/**
	 * Updates the reference based on the given state. The state is only evaluated
	 * {@link RendererReferenceNode#renderer} is not set.
	 *
	 * @param {(NodeFrame|NodeBuilder)} state - The current state.
	 * @return {Object} The updated reference.
	 */
	updateReference( state ) {

		this.reference = this.renderer !== null ? this.renderer : state.renderer;

		return this.reference;

	}

}

/**
 * TSL function for creating a renderer reference node.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the property the node refers to.
 * @param {string} type - The uniform type that should be used to represent the property value.
 * @param {?Renderer} [renderer=null] - The renderer the property belongs to. When no renderer is set,
 * the node refers to the renderer of the current state.
 * @returns {RendererReferenceNode}
 */
const rendererReference = ( name, type, renderer = null ) => new RendererReferenceNode( name, type, renderer );

/**
 * This node represents a tone mapping operation.
 *
 * @augments Node
 */
class ToneMappingNode extends Node {

	static get type() {

		return 'ToneMappingNode';

	}

	/**
	 * Constructs a new tone mapping node.
	 *
	 * @param {number} toneMapping - The tone mapping type.
	 * @param {Node} exposureNode - The tone mapping exposure.
	 * @param {Node} [colorNode=null] - The color node to process.
	 */
	constructor( toneMapping, exposureNode = toneMappingExposure, colorNode = null ) {

		super( 'vec3' );

		/**
		 * The tone mapping type.
		 *
		 * @private
		 * @type {number}
		 */
		this._toneMapping = toneMapping;

		/**
		 * The tone mapping exposure.
		 *
		 * @type {Node}
		 * @default null
		 */
		this.exposureNode = exposureNode;

		/**
		 * Represents the color to process.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.colorNode = colorNode;

	}

	/**
	 * Overwrites the default `customCacheKey()` implementation by including the tone
	 * mapping type into the cache key.
	 *
	 * @return {number} The hash.
	 */
	customCacheKey() {

		return hash$1( this._toneMapping );

	}

	/**
	 * Sets the tone mapping type.
	 *
	 * @param {number} value - The tone mapping type.
	 * @return {ToneMappingNode} A reference to this node.
	 */
	setToneMapping( value ) {

		this._toneMapping = value;

		return this;

	}

	/**
	 * Gets the tone mapping type.
	 *
	 * @returns {number} The tone mapping type.
	 */
	getToneMapping() {

		return this._toneMapping;

	}

	setup( builder ) {

		const colorNode = this.colorNode || builder.context.color;
		const toneMapping = this._toneMapping;

		if ( toneMapping === NoToneMapping ) return colorNode;

		let outputNode = null;

		const toneMappingFn = builder.renderer.library.getToneMappingFunction( toneMapping );

		if ( toneMappingFn !== null ) {

			outputNode = vec4( toneMappingFn( colorNode.rgb, this.exposureNode ), colorNode.a );

		} else {

			error( 'ToneMappingNode: Unsupported Tone Mapping configuration.', toneMapping );

			outputNode = colorNode;

		}

		return outputNode;

	}

}

/**
 * TSL function for creating a tone mapping node.
 *
 * @tsl
 * @function
 * @param {number} mapping - The tone mapping type.
 * @param {Node<float> | number} exposure - The tone mapping exposure.
 * @param {Node<vec3> | Color} color - The color node to process.
 * @returns {ToneMappingNode<vec3>}
 */
const toneMapping = ( mapping, exposure, color ) => new ToneMappingNode( mapping, nodeObject( exposure ), nodeObject( color ) );

/**
 * TSL object that represents the global tone mapping exposure of the renderer.
 *
 * @tsl
 * @type {RendererReferenceNode<vec3>}
 */
const toneMappingExposure = /*@__PURE__*/ rendererReference( 'toneMappingExposure', 'float' );

addMethodChaining( 'toneMapping', ( color, mapping, exposure ) => toneMapping( mapping, exposure, color ) );

/**
 * Internal buffer attribute library.
 *
 * @private
 * @type {WeakMap<TypedArray, InterleavedBuffer>}
 */
const _bufferLib = new WeakMap();

/**
 * Internal method for retrieving or creating interleaved buffers.
 *
 * @private
 * @param {TypedArray} value - The attribute data.
 * @param {number} itemSize - The attribute item size.
 * @returns {InterleavedBuffer} The interleaved buffer.
 */
function _getBufferAttribute( value, itemSize ) {

	let buffer = _bufferLib.get( value );

	if ( buffer === undefined ) {

		buffer = new InterleavedBuffer( value, itemSize );

		_bufferLib.set( value, buffer );

	}

	return buffer;

}

/**
 * In earlier `three.js` versions it was only possible to define attribute data
 * on geometry level. With `BufferAttributeNode`, it is also possible to do this
 * on the node level.
 * ```js
 * const geometry = new THREE.PlaneGeometry();
 * const positionAttribute = geometry.getAttribute( 'position' );
 *
 * const colors = [];
 * for ( let i = 0; i < position.count; i ++ ) {
 * 	colors.push( 1, 0, 0 );
 * }
 *
 * material.colorNode = bufferAttribute( new THREE.Float32BufferAttribute( colors, 3 ) );
 * ```
 * This new approach is especially interesting when geometry data are generated via
 * compute shaders. The below line converts a storage buffer into an attribute node.
 * ```js
 * material.positionNode = positionBuffer.toAttribute();
 * ```
 * @augments InputNode
 */
class BufferAttributeNode extends InputNode {

	static get type() {

		return 'BufferAttributeNode';

	}

	/**
	 * Constructs a new buffer attribute node.
	 *
	 * @param {BufferAttribute|InterleavedBufferAttribute|InterleavedBuffer|TypedArray} value - The attribute data.
	 * @param {?string} [bufferType=null] - The buffer type (e.g. `'vec3'`).
	 * @param {number} [bufferStride=0] - The buffer stride.
	 * @param {number} [bufferOffset=0] - The buffer offset.
	 */
	constructor( value, bufferType = null, bufferStride = 0, bufferOffset = 0 ) {

		super( value, bufferType );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isBufferNode = true;

		/**
		 * The buffer type (e.g. `'vec3'`).
		 *
		 * @type {?string}
		 * @default null
		 */
		this.bufferType = bufferType;

		/**
		 * The buffer stride.
		 *
		 * @type {number}
		 * @default 0
		 */
		this.bufferStride = bufferStride;

		/**
		 * The buffer offset.
		 *
		 * @type {number}
		 * @default 0
		 */
		this.bufferOffset = bufferOffset;

		/**
		 * The usage property. Set this to `THREE.DynamicDrawUsage` via `.setUsage()`,
		 * if you are planning to update the attribute data per frame.
		 *
		 * @type {number}
		 * @default StaticDrawUsage
		 */
		this.usage = StaticDrawUsage;

		/**
		 * Whether the attribute is instanced or not.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.instanced = false;

		/**
		 * A reference to the buffer attribute.
		 *
		 * @type {?BufferAttribute}
		 * @default null
		 */
		this.attribute = null;

		/**
		 * `BufferAttributeNode` sets this property to `true` by default.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

		if ( value && value.isBufferAttribute === true && value.itemSize <= 4 ) {

			this.attribute = value;
			this.usage = value.usage;
			this.instanced = value.isInstancedBufferAttribute;

		} else if ( value && value.isInterleavedBufferAttribute === true ) {

			// e.g. a column of a buffer attribute with an item size larger than four

			this.attribute = value;
			this.usage = value.data.usage;

		}

	}

	/**
	 * This method is overwritten since the attribute data might be shared
	 * and thus the hash should be shared as well.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The hash.
	 */
	getHash( builder ) {

		let id;

		if ( this.bufferStride === 0 && this.bufferOffset === 0 ) {

			let bufferData = builder.globalCache.getData( this.value );

			if ( bufferData === undefined ) {

				bufferData = {
					node: this
				};

				builder.globalCache.setData( this.value, bufferData );

			}

			id = bufferData.node.id;

		} else {

			id = this.id;

		}

		return String( id );

	}

	/**
	 * This method is overwritten since the node type is inferred from
	 * the buffer attribute.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		if ( this.bufferType === null ) {

			this.bufferType = builder.getTypeFromAttribute( this.attribute );

		}

		return this.bufferType;

	}

	/**
	 * Depending on which value was passed to the node, `setup()` behaves
	 * differently. If no instance of `BufferAttribute` was passed, the method
	 * creates an internal attribute and configures it respectively.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setup( builder ) {

		if ( this.attribute !== null ) return;

		//

		const type = this.getNodeType( builder );
		const itemSize = builder.getTypeLength( type );
		const value = this.value;
		const stride = this.bufferStride || itemSize;
		const offset = this.bufferOffset;

		let buffer;

		if ( value.isInterleavedBuffer === true ) {

			buffer = value;

		} else if ( value.isBufferAttribute === true ) {

			buffer = _getBufferAttribute( value.array, stride );

		} else {

			buffer = _getBufferAttribute( value, stride );

		}

		const bufferAttribute = new InterleavedBufferAttribute( buffer, itemSize, offset );

		buffer.setUsage( this.usage );

		this.attribute = bufferAttribute;
		this.attribute.isInstancedBufferAttribute = this.instanced; // @TODO: Add a possible: InstancedInterleavedBufferAttribute

	}

	/**
	 * Generates the code snippet of the buffer attribute node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The generated code snippet.
	 */
	generate( builder ) {

		const nodeType = this.getNodeType( builder );
		const nodeName = builder.context.nodeName;

		if ( nodeName !== undefined ) delete builder.context.nodeName; // deleting when consumed

		const nodeAttribute = builder.getBufferAttributeFromNode( this, nodeType, nodeName );
		const propertyName = builder.getPropertyName( nodeAttribute );

		let output = null;

		if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) {

			this.name = propertyName;

			output = propertyName;

		} else {

			let varyingName;

			if ( nodeName ) {

				varyingName = nodeName + 'Varying';

			}

			const nodeVarying = varying( this, varyingName );

			output = nodeVarying.build( builder, nodeType );

		}

		return output;

	}

	/**
	 * Overwrites the default implementation to return a fixed value `'bufferAttribute'`.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( /*builder*/ ) {

		return 'bufferAttribute';

	}

	/**
	 * Sets the `usage` property to the given value.
	 *
	 * @param {number} value - The usage to set.
	 * @return {BufferAttributeNode} A reference to this node.
	 */
	setUsage( value ) {

		this.usage = value;

		if ( this.attribute && this.attribute.isBufferAttribute === true ) {

			this.attribute.usage = value;

		}

		return this;

	}

	/**
	 * Sets the `instanced` property to the given value.
	 *
	 * @param {boolean} value - The value to set.
	 * @return {BufferAttributeNode} A reference to this node.
	 */
	setInstanced( value ) {

		this.instanced = value;

		return this;

	}

	/**
	 * Returns the buffer attribute to bind when rendering the given object. Nodes that
	 * bind attributes of the rendered object, like {@link ReferenceAttributeNode}, overwrite it.
	 *
	 * @param {Object3D} object - The rendered object.
	 * @return {?(BufferAttribute|InterleavedBufferAttribute)} The buffer attribute.
	 */
	getObjectAttribute( /*object*/ ) {

		return this.attribute;

	}

}

/**
 * Internal method for creating buffer attribute nodes.
 *
 * @private
 * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
 * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
 * @param {number} [stride=0] - The buffer stride.
 * @param {number} [offset=0] - The buffer offset.
 * @param {number} [usage=StaticDrawUsage] - The buffer usage.
 * @param {boolean} [instanced=false] - Whether the buffer is instanced.
 * @returns {BufferAttributeNode|Node} The buffer attribute node.
 */
function createBufferAttribute( array, type = null, stride = 0, offset = 0, usage = StaticDrawUsage, instanced = false ) {

	if ( type === 'mat3' || ( type === null && array.itemSize === 9 ) ) {

		return mat3(
			new BufferAttributeNode( array, 'vec3', 9, 0 ).setUsage( usage ).setInstanced( instanced ),
			new BufferAttributeNode( array, 'vec3', 9, 3 ).setUsage( usage ).setInstanced( instanced ),
			new BufferAttributeNode( array, 'vec3', 9, 6 ).setUsage( usage ).setInstanced( instanced )
		);

	} else if ( type === 'mat4' || ( type === null && array.itemSize === 16 ) ) {

		return mat4(
			new BufferAttributeNode( array, 'vec4', 16, 0 ).setUsage( usage ).setInstanced( instanced ),
			new BufferAttributeNode( array, 'vec4', 16, 4 ).setUsage( usage ).setInstanced( instanced ),
			new BufferAttributeNode( array, 'vec4', 16, 8 ).setUsage( usage ).setInstanced( instanced ),
			new BufferAttributeNode( array, 'vec4', 16, 12 ).setUsage( usage ).setInstanced( instanced )
		);

	}

	return new BufferAttributeNode( array, type, stride, offset ).setUsage( usage );

}

/**
 * TSL function for creating a buffer attribute node.
 *
 * @tsl
 * @function
 * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
 * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
 * @param {number} [stride=0] - The buffer stride.
 * @param {number} [offset=0] - The buffer offset.
 * @returns {BufferAttributeNode|Node}
 */
const bufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => createBufferAttribute( array, type, stride, offset );

/**
 * TSL function for creating a buffer attribute node but with dynamic draw usage.
 * Use this function if attribute data are updated per frame.
 *
 * @tsl
 * @function
 * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
 * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
 * @param {number} [stride=0] - The buffer stride.
 * @param {number} [offset=0] - The buffer offset.
 * @returns {BufferAttributeNode|Node}
 */
const dynamicBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => createBufferAttribute( array, type, stride, offset, DynamicDrawUsage );

/**
 * TSL function for creating a buffer attribute node but with enabled instancing
 *
 * @tsl
 * @function
 * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
 * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
 * @param {number} [stride=0] - The buffer stride.
 * @param {number} [offset=0] - The buffer offset.
 * @returns {BufferAttributeNode|Node}
 */
const instancedBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => createBufferAttribute( array, type, stride, offset, StaticDrawUsage, true );

/**
 * TSL function for creating a buffer attribute node but with dynamic draw usage and enabled instancing
 *
 * @tsl
 * @function
 * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
 * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
 * @param {number} [stride=0] - The buffer stride.
 * @param {number} [offset=0] - The buffer offset.
 * @returns {BufferAttributeNode|Node}
 */
const instancedDynamicBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => createBufferAttribute( array, type, stride, offset, DynamicDrawUsage, true );

addMethodChaining( 'toAttribute', ( bufferNode ) => bufferAttribute( bufferNode.value, bufferNode.bufferType ) );

/**
 * This class represents shader indices of different types. The following predefined node
 * objects cover frequent use cases:
 *
 * - `vertexIndex`: The index of a vertex within a mesh.
 * - `instanceIndex`: The index of either a mesh instance or an invocation of a compute shader.
 * - `drawIndex`: The index of a draw call.
 * - `invocationLocalIndex`: The index of a compute invocation within the scope of a workgroup.
 * - `invocationSubgroupIndex`: The index of a compute invocation within the scope of a subgroup.
 * - `subgroupIndex`: The index of a compute invocation's subgroup within its workgroup.
 *
 * @augments Node
 */
class IndexNode extends Node {

	static get type() {

		return 'IndexNode';

	}

	/**
	 * Constructs a new index node.
	 *
	 * @param {('vertex'|'instance'|'subgroup'|'invocationLocal'|'invocationSubgroup'|'draw')} scope - The scope of the index node.
	 */
	constructor( scope ) {

		super( 'uint' );

		/**
		 * The scope of the index node.
		 *
		 * @type {string}
		 */
		this.scope = scope;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isIndexNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generate( builder ) {

		const nodeType = this.getNodeType( builder );
		const scope = this.scope;

		let propertyName;

		if ( scope === IndexNode.VERTEX ) {

			propertyName = builder.getVertexIndex();

		} else if ( scope === IndexNode.INSTANCE ) {

			propertyName = builder.getInstanceIndex();

		} else if ( scope === IndexNode.DRAW ) {

			propertyName = builder.getDrawIndex();

		} else if ( scope === IndexNode.INVOCATION_LOCAL ) {

			propertyName = builder.getInvocationLocalIndex();

		} else if ( scope === IndexNode.INVOCATION_SUBGROUP ) {

			propertyName = builder.getInvocationSubgroupIndex();

		} else if ( scope === IndexNode.SUBGROUP ) {

			propertyName = builder.getSubgroupIndex();

		} else {

			throw new Error( 'THREE.IndexNode: Unknown scope: ' + scope );

		}

		let output;

		if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) {

			output = propertyName;

		} else {

			const nodeVarying = varying( this );

			output = nodeVarying.build( builder, nodeType );

		}

		return output;

	}

}

IndexNode.VERTEX = 'vertex';
IndexNode.INSTANCE = 'instance';
IndexNode.SUBGROUP = 'subgroup';
IndexNode.INVOCATION_LOCAL = 'invocationLocal';
IndexNode.INVOCATION_SUBGROUP = 'invocationSubgroup';
IndexNode.DRAW = 'draw';

/**
 * TSL object that represents the index of a vertex within a mesh.
 *
 * @tsl
 * @type {IndexNode}
 */
const vertexIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.VERTEX );

/**
 * TSL object that contextually represents specific index data depending on the shader stage.
 *
 * Within the vertex and fragment stages, `instanceIndex` will represent the index of the current mesh instance being evaluated by the shader.
 * In these stages, use `instanceIndex` to modify a mesh based on its instance or to select per-instance data.
 *
 * ```js
 * // instanceIndex will equal the current mesh's instance index between 0-500
 * const material = new THREE.BasicNodeMaterial();
 * material.positionNode = vec3( instanceIndex.mod( 10 ), instanceIndex.div( 10 ), 0 );
 * const mesh = new THREE.InstancedMesh( geometry, material, 500 );
 * ```
 *
 * Within the compute stage, `instanceIndex` will represent the global index of a compute invocation within the 3-dimensional compute workgroup load.
 * In this stage, use `instanceIndex` to modify or select data at a given index within a buffer, or derive values from the index itself.
 *
 * ```js
 * // instanceIndex will equal value between 0 - 255
 * const computeFn = Fn() => {
 *
 * 	storageBuffer.element( instanceIndex ).assign( instanceIndex );
 *
 * } )().compute( 255 )
 * ```
 *
 * @tsl
 * @type {IndexNode}
 */
const instanceIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INSTANCE );

/**
 * TSL object that represents the index of the subgroup the current compute invocation belongs to.
 * Subgroup indices are local to the workgroups to which they belong.
 *
 * ```js
 * // Execute 12 compute threads with a workgroup size of 9. Example assumes a subgroup size of 3.
 * const computeFn = Fn( () => {
 *
 * 	storageBufferOne.element( instanceIndex ).assign( subgroupIndex );
 * 	storageBufferTwo.element( instanceIndex ).assign( workgroupId.x );
 *
 * } )().compute( 12, [ 9 ] );
 *
 * // instanceIndex =  [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ];
 * // Buffer One ( Subgroup Index ) =  [ 0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0 ];
 * // Buffer Two ( Workgroup ID )   =  [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 ];
 * ```
 *
 * @tsl
 * @type {IndexNode}
 */
const subgroupIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.SUBGROUP );

/**
 * TSL object that represents the index of a compute invocation within the scope of a subgroup.
 *
 * ```js
 * // Execute 12 compute threads with a workgroup size of 12. Example assumes a subgroup size of 3.
 * const computeFn = Fn( () => {
 *
 * 	storageBufferOne.element( instanceIndex ).assign( invocationSubgroupIndex );
 * 	storageBufferTwo.element( instanceIndex ).assign( subgroupIndex );
 *
 * } )().compute( 12, [ 12 ] );
 *
 * // instanceIndex =  [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ];
 * // Buffer One ( Invocation Subgroup Index ) =  [ 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2 ];
 * // Buffer Two ( Subgroup Index )            =  [ 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3 ];
 * ```
 *
 * @tsl
 * @type {IndexNode}
 */
const invocationSubgroupIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INVOCATION_SUBGROUP );

/**
 * TSL object that represents the index of a compute invocation within the scope of a workgroup.
 *
 * ```js
 * // Execute 12 compute threads with a workgroup size of 4.
 * const computeFn = Fn( () => {
 *
 * 	storageBufferOne.element( instanceIndex ).assign( invocationLocalIndex );
 * 	storageBufferTwo.element( instanceIndex ).assign( workgroupId.x );
 *
 * } )().compute( 12, [ 4 ] );
 *
 * // instanceIndex =  [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ];
 * // Buffer One ( Invocation Local Index ) =     [ 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3 ];
 * // Buffer Two ( Workgroup ID )           =     [ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2 ];
 * ```
 *
 * @tsl
 * @type {IndexNode}
 */
const invocationLocalIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INVOCATION_LOCAL );

/**
 * TSL object that represents the index of a draw call.
 *
 * @tsl
 * @type {IndexNode}
 */
const drawIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.DRAW );

/**
 * Represents a compute shader node.
 *
 * @augments Node
 */
class ComputeNode extends Node {

	static get type() {

		return 'ComputeNode';

	}

	/**
	 * Constructs a new compute node.
	 *
	 * @param {Node} computeNode - The node that defines the compute shader logic.
	 * @param {Array<number>} workgroupSize - An array defining the X, Y, and Z dimensions of the workgroup for compute shader execution.
	 */
	constructor( computeNode, workgroupSize ) {

		super( 'void' );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isComputeNode = true;

		/**
		 * The node that defines the compute shader logic.
		 *
		 * @type {Node}
		 */
		this.computeNode = computeNode;

		/**
		 * An array defining the X, Y, and Z dimensions of the workgroup for compute shader execution.
		 *
		 * @type {Array<number>}
		 * @default [ 64 ]
		 */
		this.workgroupSize = workgroupSize;

		/**
		 * The total number of threads (invocations) to execute. If it is a number, it will be used
		 * to automatically generate bounds checking against `instanceIndex`.
		 *
		 * @type {number|Array<number>}
		 */
		this.count = null;

		/**
		 * The dispatch size for workgroups on X, Y, and Z axes.
		 * Used directly if `count` is not provided.
		 *
		 * @type {number|Array<number>}
		 */
		this.dispatchSize = null;

		/**
		 * The version of the node.
		 *
		 * @type {number}
		 */
		this.version = 1;

		/**
		 * The name or label of the uniform.
		 *
		 * @type {string}
		 * @default ''
		 */
		this.name = '';

		/**
		 * A callback executed when the compute node finishes initialization.
		 *
		 * @type {?Function}
		 */
		this.onInitFunction = null;

		/**
		 * A uniform node holding the dispatch count for bounds checking.
		 * Created automatically when `count` is a number.
		 *
		 * @type {?UniformNode}
		 */
		this.countNode = null;

		/**
		 * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node computes
		 * once per frame.
		 *
		 * @type {string}
		 * @default 'frame'
		 */
		this.updateBeforeType = NodeUpdateType.FRAME;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Sets the {@link ComputeNode#name} property.
	 *
	 * @param {string} name - The name of the uniform.
	 * @return {ComputeNode} A reference to this node.
	 */
	setName( name ) {

		this.name = name;

		return this;

	}

	/**
	 * Sets the {@link ComputeNode#name} property.
	 *
	 * @deprecated
	 * @param {string} name - The name of the uniform.
	 * @return {ComputeNode} A reference to this node.
	 */
	label( name ) {

		warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.', new StackTrace() ); // @deprecated r179

		return this.setName( name );

	}

	/**
	 * Sets the callback to run during initialization.
	 *
	 * @param {Function} callback - The callback function.
	 * @return {ComputeNode} A reference to this node.
	 */
	onInit( callback ) {

		this.onInitFunction = callback;

		return this;

	}

	/**
	 * Returns the update type of {@link Node#updateBefore}.
	 *
	 * @param {NodeFrame} [frame] - The current node frame.
	 * @return {NodeUpdateType} The update type.
	 */
	getUpdateBeforeType( frame ) {

		return frame.compute !== this ? this.updateBeforeType : NodeUpdateType.NONE;

	}

	/**
	 * The method execute the compute for this node.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	updateBefore( { renderer } ) {

		renderer.compute( this );

	}

	setup( builder ) {

		if ( this.count !== null && this.countNode === null ) {

			this.countNode = uniform( this.count, 'uint' ).onObjectUpdate( () => this.count );

		}

		const result = this.computeNode.build( builder );

		if ( result ) {

			const properties = builder.getNodeProperties( this );
			properties.outputComputeNode = result.outputNode;

			result.outputNode = null;

		}

		return result;

	}

	generate( builder, output ) {

		const { shaderStage } = builder;

		if ( shaderStage === 'compute' ) {

			const snippet = this.computeNode.build( builder, 'void' );

			if ( snippet !== '' ) {

				builder.addLineFlowCode( snippet, this );

			}

			if ( this.count !== null && builder.allowEarlyReturns === true ) {

				const countSnippet = this.countNode.build( builder, 'uint' );
				const indexSnippet = instanceIndex.build( builder, 'uint' );

				builder.flow.code = `${ builder.tab }if ( ${ indexSnippet } >= ${ countSnippet } ) { return; }\n\n${ builder.flow.code }`;

			}


		} else {

			const properties = builder.getNodeProperties( this );
			const outputComputeNode = properties.outputComputeNode;

			if ( outputComputeNode ) {

				return outputComputeNode.build( builder, output );

			}

		}

	}

}

/**
 * TSL function for creating a compute kernel node.
 *
 * @tsl
 * @function
 * @param {Node} node - The TSL logic for the compute shader.
 * @param {Array<number>} [workgroupSize=[64]] - The workgroup size.
 * @returns {ComputeNode}
 */
const computeKernel = ( node, workgroupSize = [ 64 ] ) => {

	if ( workgroupSize.length === 0 || workgroupSize.length > 3 ) {

		error( 'TSL: compute() workgroupSize must have 1, 2, or 3 elements', new StackTrace() );

	}

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

		const val = workgroupSize[ i ];

		if ( typeof val !== 'number' || val <= 0 || ! Number.isInteger( val ) ) {

			error( `TSL: compute() workgroupSize element at index [ ${ i } ] must be a positive integer`, new StackTrace() );

		}

	}

	// Implicit fill-up to [ x, y, z ] with 1s, just like WGSL treats @workgroup_size when fewer dimensions are specified

	while ( workgroupSize.length < 3 ) workgroupSize.push( 1 );

	//

	return new ComputeNode( nodeObject( node ), workgroupSize );

};

/**
 * TSL function for creating a compute node.
 *
 * @tsl
 * @function
 * @param {Node} node - The TSL logic for the compute shader.
 * @param {number|Array<number>} count - The compute count or dispatch size.
 * @param {Array<number>} [workgroupSize=[64]] - The workgroup size.
 * @returns {ComputeNode}
,  */
const compute = ( node, count, workgroupSize ) => {

	const computeNode = computeKernel( node, workgroupSize );

	if ( typeof count === 'number' ) {

		computeNode.count = count;

	} else {

		computeNode.dispatchSize = count;

	}

	return computeNode;

};

addMethodChaining( 'compute', compute );
addMethodChaining( 'computeKernel', computeKernel );

/**
 * This node can be used as a cache management component for another node.
 * Caching is in general used by default in {@link NodeBuilder} but this node
 * allows the usage of a shared parent cache during the build process.
 *
 * @augments Node
 */
class IsolateNode extends Node {

	static get type() {

		return 'IsolateNode';

	}

	/**
	 * Constructs a new cache node.
	 *
	 * @param {Node} node - The node that should be cached.
	 * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not.
	 */
	constructor( node, parent = true ) {

		super();

		/**
		 * The node that should be cached.
		 *
		 * @type {Node}
		 */
		this.node = node;

		/**
		 * Whether this node refers to a shared parent cache or not.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.parent = parent;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isIsolateNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generateNodeType( builder ) {

		const previousCache = builder.getCache();
		const cache = builder.getCacheFromNode( this, this.parent );

		builder.setCache( cache );

		const nodeType = this.node.getNodeType( builder );

		builder.setCache( previousCache );

		return nodeType;

	}

	build( builder, ...params ) {

		this._setupInspector( builder );

		const previousCache = builder.getCache();
		const cache = builder.getCacheFromNode( this, this.parent );

		builder.setCache( cache );

		const data = this.node.build( builder, ...params );

		builder.setCache( previousCache );

		return data;

	}

	setParent( parent ) {

		this.parent = parent;

		return this;

	}

	getParent() {

		return this.parent;

	}

}

/**
 * TSL function for creating a cache node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node that should be cached.
 * @returns {IsolateNode}
 */
const isolate = ( node ) => new IsolateNode( nodeObject( node ) );

/**
 * TSL function for creating a cache node.
 *
 * @tsl
 * @function
 * @deprecated
 * @param {Node} node - The node that should be cached.
 * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not.
 * @returns {IsolateNode}
 */
function cache( node, parent = true ) {

	warn( 'TSL: "cache()" has been deprecated. Use "isolate()" instead.' ); // @deprecated r181

	return isolate( node ).setParent( parent );

}

addMethodChaining( 'cache', cache );
addMethodChaining( 'isolate', isolate );

/**
 * The class generates the code of a given node but returns another node in the output.
 * This can be used to call a method or node that does not return a value, i.e.
 * type `void` on an input where returning a value is required. Example:
 *
 * ```js
 * material.colorNode = myColor.bypass( runVoidFn() )
 *```
 *
 * @augments Node
 */
class BypassNode extends Node {

	static get type() {

		return 'BypassNode';

	}

	/**
	 * Constructs a new bypass node.
	 *
	 * @param {Node} outputNode - The output node.
	 * @param {Node} callNode - The call node.
	 */
	constructor( outputNode, callNode ) {

		super();

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isBypassNode = true;

		/**
		 * The output node.
		 *
		 * @type {Node}
		 */
		this.outputNode = outputNode;

		/**
		 * The call node.
		 *
		 * @type {Node}
		 */
		this.callNode = callNode;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generateNodeType( builder ) {

		return this.outputNode.getNodeType( builder );

	}

	generate( builder ) {

		const snippet = this.callNode.build( builder, 'void' );

		if ( snippet !== '' ) {

			builder.addLineFlowCode( snippet, this );

		}

		return this.outputNode.build( builder );

	}

}

/**
 * TSL function for creating a bypass node.
 *
 * @tsl
 * @function
 * @param {Node} outputNode - The output node.
 * @param {Node} callNode - The call node.
 * @returns {BypassNode}
 */
const bypass = /*@__PURE__*/ nodeProxy( BypassNode ).setParameterLength( 2 );

addMethodChaining( 'bypass', bypass );

/**
 * This node allows to remap a node value from one range into another. E.g a value of
 * `0.4` in the range `[ 0.3, 0.5 ]` should be remapped into the normalized range `[ 0, 1 ]`.
 * `remap` takes care of that and converts the original value of `0.4` to `0.5`.
 *
 * @tsl
 * @function
 * @param {Node} node - The node that should be remapped.
 * @param {Node} inLowNode - The source or current lower bound of the range.
 * @param {Node} inHighNode - The source or current upper bound of the range.
 * @param {?Node} [outLowNode=float(0)] - The target lower bound of the range.
 * @param {?Node} [outHighNode=float(1)] - The target upper bound of the range.
 * @returns {Node}
 */
const remap = /*@__PURE__*/ Fn( ( [ node, inLowNode, inHighNode, outLowNode = float( 0 ), outHighNode = float( 1 ), doClamp = bool( false ) ] ) => {

	let t = node.sub( inLowNode ).div( inHighNode.sub( inLowNode ) );

	if ( defined( doClamp ) ) t = t.clamp();

	return t.mul( outHighNode.sub( outLowNode ) ).add( outLowNode );

} );

/**
 * This node allows to remap a node value from one range into another but with enabled clamping. E.g a value of
 * `0.4` in the range `[ 0.3, 0.5 ]` should be remapped into the normalized range `[ 0, 1 ]`.
 * `remapClamp` takes care of that and converts the original value of `0.4` to `0.5`.
 *
 * @tsl
 * @function
 * @param {Node} node - The node that should be remapped.
 * @param {Node} inLowNode - The source or current lower bound of the range.
 * @param {Node} inHighNode - The source or current upper bound of the range.
 * @param {?Node} [outLowNode=float(0)] - The target lower bound of the range.
 * @param {?Node} [outHighNode=float(1)] - The target upper bound of the range.
 * @returns {Node}
 */
function remapClamp( node, inLowNode, inHighNode, outLowNode = float( 0 ), outHighNode = float( 1 ) ) {

	return remap( node, inLowNode, inHighNode, outLowNode, outHighNode, true );

}

addMethodChaining( 'remap', remap );
addMethodChaining( 'remapClamp', remapClamp );

/**
 * This class can be used to implement basic expressions in shader code.
 * Basic examples for that are `return`, `continue` or `discard` statements.
 *
 * @augments Node
 */
class ExpressionNode extends Node {

	static get type() {

		return 'ExpressionNode';

	}

	/**
	 * Constructs a new expression node.
	 *
	 * @param {string} [snippet=''] - The native code snippet.
	 * @param {string} [nodeType='void'] - The node type.
	 */
	constructor( snippet = '', nodeType = 'void' ) {

		super( nodeType );

		/**
		 * The native code snippet.
		 *
		 * @type {string}
		 * @default ''
		 */
		this.snippet = snippet;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generate( builder, output ) {

		const type = this.getNodeType( builder );
		const snippet = this.snippet;

		if ( type === 'void' ) {

			builder.addLineFlowCode( snippet, this );

		} else {

			return builder.format( snippet, type, output );

		}

	}

}

/**
 * TSL function for creating an expression node.
 *
 * @tsl
 * @function
 * @param {string} [snippet] - The native code snippet.
 * @param {?string} [nodeType='void'] - The node type.
 * @returns {ExpressionNode}
 */
const expression = /*@__PURE__*/ nodeProxy( ExpressionNode ).setParameterLength( 1, 2 );

/**
 * Represents a `discard` shader operation in TSL.
 *
 * @tsl
 * @function
 * @param {?ConditionalNode} conditional - An optional conditional node. It allows to decide whether the discard should be executed or not.
 * @return {Node} The `discard` expression.
 */
const Discard = ( conditional ) => ( conditional ? select( conditional, expression( 'discard' ) ) : expression( 'discard' ) ).toStack();

/**
 * Represents a `return` shader operation in TSL.
 *
 * @tsl
 * @function
 * @return {ExpressionNode} The `return` expression.
 */
const Return = () => expression( 'return' ).toStack();

addMethodChaining( 'discard', Discard );

/**
 * Premultiplies the RGB channels of a color by its alpha channel.
 *
 * This function is useful for converting a non-premultiplied alpha color
 * into a premultiplied alpha format, where the RGB values are scaled
 * by the alpha value. Premultiplied alpha is often used in graphics
 * rendering for certain operations, such as compositing and image processing.
 *
 * @tsl
 * @function
 * @param {Node<vec4>} color - The input color with non-premultiplied alpha.
 * @return {Node<vec4>} The color with premultiplied alpha.
 */
const premultiplyAlpha = /*@__PURE__*/ Fn( ( [ color ] ) => {

	return vec4( color.rgb.mul( color.a ), color.a );

}, { color: 'vec4', return: 'vec4' } );

/**
 * Unpremultiplies the RGB channels of a color by its alpha channel.
 *
 * This function is useful for converting a premultiplied alpha color
 * back into a non-premultiplied alpha format, where the RGB values are
 * divided by the alpha value. Unpremultiplied alpha is often used in graphics
 * rendering for certain operations, such as compositing and image processing.
 *
 * @tsl
 * @function
 * @param {Node<vec4>} color - The input color with premultiplied alpha.
 * @return {Node<vec4>} The color with non-premultiplied alpha.
 */
const unpremultiplyAlpha = /*@__PURE__*/ Fn( ( [ color ] ) => {

	return color.a.equal( 0 ).select( vec4( 0 ), vec4( color.rgb.div( color.a ), color.a ) );

}, { color: 'vec4', return: 'vec4' } );

/**
 * Normally, tone mapping and color conversion happens automatically just
 * before outputting a pixel to the default (screen) framebuffer. In certain
 * post processing setups this is too late because some effects such as FXAA
 * require e.g. sRGB input. For such scenarios, `RenderOutputNode` can be used
 * to apply tone mapping and color space conversion at an arbitrary point
 * in the effect chain.
 *
 * When applying tone mapping and color space conversion manually with this node,
 * you have to set {@link RenderPipeline#outputColorTransform} to `false`.
 *
 * ```js
 * const postProcessing = new RenderPipeline( renderer );
 * postProcessing.outputColorTransform = false;
 *
 * const scenePass = pass( scene, camera );
 * const outputPass = renderOutput( scenePass );
 *
 * postProcessing.outputNode = outputPass;
 * ```
 *
 * @augments Node
 */
class RenderOutputNode extends Node {

	static get type() {

		return 'RenderOutputNode';

	}

	/**
	 * Constructs a new render output node.
	 *
	 * @param {Node} colorNode - The color node to process.
	 * @param {?number} toneMapping - The tone mapping type.
	 * @param {?string} outputColorSpace - The output color space.
	 */
	constructor( colorNode, toneMapping, outputColorSpace ) {

		super( 'vec4' );

		/**
		 * The color node to process.
		 *
		 * @type {Node}
		 */
		this.colorNode = colorNode;

		/**
		 * The tone mapping type.
		 *
		 * @private
		 * @type {?number}
		 */
		this._toneMapping = toneMapping;

		/**
		 * The output color space.
		 *
		 * @type {?string}
		 */
		this.outputColorSpace = outputColorSpace;

		/**
		 * Whether the color node uses premultiplied alpha. The output
		 * uses the same alpha mode.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.premultipliedAlpha = true;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isRenderOutputNode = true;

	}

	/**
	 * Sets the tone mapping type.
	 *
	 * @param {number} value - The tone mapping type.
	 * @return {ToneMappingNode} A reference to this node.
	 */
	setToneMapping( value ) {

		this._toneMapping = value;

		return this;

	}

	/**
	 * Gets the tone mapping type.
	 *
	 * @returns {number} The tone mapping type.
	 */
	getToneMapping() {

		return this._toneMapping;

	}

	setup( { context } ) {

		let outputNode = this.colorNode || context.color;

		// clamp alpha
		outputNode = vec4( outputNode.rgb, outputNode.a.clamp( 0.0, 1.0 ) );

		// unpremultiply
		if ( this.premultipliedAlpha === true ) {

			outputNode = unpremultiplyAlpha( outputNode );

		}

		// tone mapping
		const toneMapping = ( this._toneMapping !== null ? this._toneMapping : context.toneMapping ) || NoToneMapping;

		const outputColorSpace = ( this.outputColorSpace !== null ? this.outputColorSpace : context.outputColorSpace ) || NoColorSpace;

		if ( toneMapping !== NoToneMapping ) {

			outputNode = outputNode.toneMapping( toneMapping );

		}

		// working to output color space
		if ( outputColorSpace !== NoColorSpace && outputColorSpace !== ColorManagement.workingColorSpace ) {

			outputNode = outputNode.workingToColorSpace( outputColorSpace );

		}

		// premultiply in output color space
		if ( this.premultipliedAlpha === true ) {

			outputNode = premultiplyAlpha( outputNode );

		}

		return outputNode;

	}

}

/**
 * TSL function for creating a render output node.
 *
 * @tsl
 * @function
 * @param {Node} color - The color node to process.
 * @param {?number} [toneMapping=null] - The tone mapping type.
 * @param {?string} [outputColorSpace=null] - The output color space.
 * @returns {RenderOutputNode}
 */
const renderOutput = ( color, toneMapping = null, outputColorSpace = null ) => new RenderOutputNode( nodeObject( color ), toneMapping, outputColorSpace );

addMethodChaining( 'renderOutput', renderOutput );

class DebugNode extends Node {

	static get type() {

		return 'DebugNode';

	}

	constructor( node, callback = null ) {

		super();

		this.node = node;
		this.callback = callback;

	}

	generateNodeType( builder ) {

		return this.node.getNodeType( builder );

	}

	setup( builder ) {

		return this.node.build( builder );

	}

	analyze( builder ) {

		return this.node.build( builder );

	}

	generate( builder ) {

		const callback = this.callback;
		const snippet = this.node.build( builder );

		if ( callback !== null ) {

			callback( builder, snippet );

		} else {

			const title = '--- TSL debug - ' + builder.shaderStage + ' shader ---';
			const border = '-'.repeat( title.length );

			let code = '';
			code += '// #' + title + '#\n';
			code += builder.flow.code.replace( /^\t/mg, '' ) + '\n';
			code += '/* ... */ ' + snippet + ' /* ... */\n';
			code += '// #' + border + '#\n';

			log$1( code );

		}

		return snippet;

	}

}

/**
 * TSL function for creating a debug node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node to debug.
 * @param {?Function} [callback=null] - Optional callback function to handle the debug output.
 * @returns {DebugNode}
 */
const debug = ( node, callback = null ) => new DebugNode( nodeObject( node ), callback ).toStack();

addMethodChaining( 'debug', debug );

/**
 * InspectorBase is the base class for all inspectors. Can only be used with {@link WebGPURenderer}.
 *
 * @augments EventDispatcher
 */
class InspectorBase extends EventDispatcher {

	/**
	 * Creates a new InspectorBase.
	 */
	constructor() {

		super();

		/**
		 * The renderer associated with this inspector.
		 *
		 * @type {Renderer}
		 * @private
		 */
		this._renderer = null;

		/**
		 * The current frame being processed.
		 *
		 * @type {Object}
		 */
		this.currentFrame = null;

		/**
		 * Indicates whether the inspector is running.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.isRunning = false;

		/**
		 * Indicates whether the inspector is enabled.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.enabled = true;

	}

	/**
	 * Returns the node frame for the current renderer.
	 *
	 * @return {Object} The node frame.
	 */
	get nodeFrame() {

		return this._renderer._nodes.nodeFrame;

	}

	/**
	 * Sets the renderer for this inspector.
	 *
	 * @param {Renderer} renderer - The renderer to associate with this inspector.
	 * @return {InspectorBase} This inspector instance.
	 */
	setRenderer( renderer ) {

		this._renderer = renderer;

		return this;

	}

	/**
	 * Returns the renderer associated with this inspector.
	 *
	 * @return {Renderer} The associated renderer.
	 */
	getRenderer() {

		return this._renderer;

	}

	/**
	 * Called when a frame begins.
	 */
	begin() {

		this.isRunning = true;

	}

	/**
	 * Called when a frame ends.
	 */
	finish() {

		this.isRunning = false;

	}

	/**
	 * Inspects a node.
	 *
	 * @param {Node} node - The node to inspect.
	 */
	inspect( /*node*/ ) { }

	/**
	 * When a compute operation is performed.
	 *
	 * @param {ComputeNode} computeNode - The compute node being executed.
	 * @param {number|Array<number>} dispatchSizeOrCount - The dispatch size or count.
	 */
	computeAsync( /*computeNode, dispatchSizeOrCount*/ ) { }

	/**
	 * Called when a compute operation begins.
	 *
	 * @param {string} uid - A unique identifier for the render context.
	 * @param {ComputeNode} computeNode - The compute node being executed.
	 */
	beginCompute( /*uid, computeNode*/ ) { }

	/**
	 * Called when a compute operation ends.
	 *
	 * @param {string} uid - A unique identifier for the render context.
	 * @param {ComputeNode} computeNode - The compute node being executed.
	 */
	finishCompute( /*uid*/ ) { }

	/**
	 * Called when a render operation begins.
	 *
	 * @param {string} uid - A unique identifier for the render context.
	 * @param {Scene} scene - The scene being rendered.
	 * @param {Camera} camera - The camera being used for rendering.
	 * @param {?RenderTarget} renderTarget - The render target, if any.
	 */
	beginRender( /*uid, scene, camera, renderTarget*/ ) { }

	/**
	 * Called when an animation loop ends.
	 *
	 * @param {string} uid - A unique identifier for the render context.
	 */
	finishRender( /*uid*/ ) { }

	/**
	 * Called when a texture copy operation is performed.
	 *
	 * @param {Texture} srcTexture - The source texture.
	 * @param {Texture} dstTexture - The destination texture.
	 */
	copyTextureToTexture( /*srcTexture, dstTexture*/ ) { }

	/**
	 * Called when a framebuffer copy operation is performed.
	 *
	 * @param {Texture} framebufferTexture - The texture associated with the framebuffer.
	 */
	copyFramebufferToTexture( /*framebufferTexture*/ ) { }

	/**
	 * Frees all internal resources of the inspector.
	 */
	dispose() {

		if ( this.isRunning === true ) this.finish();

		this.dispatchEvent( { type: 'dispose' } );

	}

}

/**
 * InspectorNode is a wrapper node that allows inspection of node values during rendering.
 * It can be used to debug or analyze node outputs in the rendering pipeline.
 *
 * The inspector node is registered by the inspected node when it is built, see {@link Node#_setupInspector}.
 *
 * @augments Node
 */
class InspectorNode extends Node {

	/**
	 * Returns the type of the node.
	 *
	 * @returns {string}
	 */
	static get type() {

		return 'InspectorNode';

	}

	/**
	 * Creates an InspectorNode.
	 *
	 * @param {Node} node - The node to inspect.
	 * @param {string} [name=''] - Optional name for the inspector node.
	 * @param {Function|null} [callback=null] - Optional callback to modify the node presented by the inspector.
	 */
	constructor( node, name = '', callback = null ) {

		super();

		this.node = node;
		this.name = name;
		this.callback = callback;

		this.updateType = NodeUpdateType.FRAME;

		this.isInspectorNode = true;

	}

	/**
	 * Returns the name of the inspector node.
	 *
	 * @returns {string}
	 */
	getName() {

		return this.name || this.node.name;

	}

	/**
	 * Updates the inspector node, allowing inspection of the wrapped node.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	update( frame ) {

		const { renderer } = frame;

		if ( renderer.backend.isWebGPUBackend !== true && renderer.inspector.constructor !== InspectorBase ) {

			warnOnce( 'TSL: ".toInspector()" is only available with WebGPU.' );

		}

		renderer.inspector.inspect( this );

	}

	/**
	 * Returns the type of the wrapped node.
	 *
	 * @param {NodeBuilder} builder - The node builder.
	 * @returns {string}
	 */
	generateNodeType( builder ) {

		return this.node.getNodeType( builder );

	}

}

/**
 * Creates an inspector node to wrap around a given node for inspection purposes.
 *
 * @tsl
 * @param {Node} node - The node to inspect.
 * @param {string} [name=''] - Optional name for the inspector node.
 * @param {Function|null} [callback=null] - Optional callback to modify the node presented by the inspector.
 * @returns {Node} The inspected node.
 */
function inspect( node, name = '', callback = null ) {

	node = nodeObject( node );

	node._inspectorNode = new InspectorNode( node, name, callback );

	return node;

}

addMethodChaining( 'toInspector', inspect );

function addNodeElement( name/*, nodeElement*/ ) {

	warn( 'TSL: AddNodeElement has been removed in favor of tree-shaking. Trying add', name );

}

/**
 * Base class for representing shader attributes as nodes.
 *
 * @augments Node
 */
class AttributeNode extends Node {

	static get type() {

		return 'AttributeNode';

	}

	/**
	 * Constructs a new attribute node.
	 *
	 * @param {string} attributeName - The name of the attribute.
	 * @param {?string} nodeType - The node type.
	 */
	constructor( attributeName, nodeType = null ) {

		super( nodeType );

		/**
		 * `AttributeNode` sets this property to `true` by default.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

		this._attributeName = attributeName;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	getHash( builder ) {

		return this.getAttributeName( builder );

	}

	/**
	 * Returns the node which provides the value of this attribute in the current context, if any.
	 * The context can provide attributes with a `getAttribute( name, builder )` function, e.g. if the
	 * vertices are pulled from a storage buffer or the surface is reconstructed per fragment. The
	 * function should return the same node for the same attribute.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {?Node} The node providing the value of the attribute.
	 */
	getContextAttribute( builder ) {

		const getAttribute = builder.context.getAttribute;

		return getAttribute ? getAttribute( this.getAttributeName( builder ), builder ) : null;

	}

	/**
	 * An attribute provided by the context is replaced by the provided node, like an override.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node} The shared node if possible. Otherwise `this` is returned.
	 */
	getShared( builder ) {

		return this.getContextAttribute( builder ) || super.getShared( builder );

	}

	generateNodeType( builder ) {

		const contextAttribute = this.getContextAttribute( builder );

		if ( contextAttribute ) return contextAttribute.getNodeType( builder );

		let nodeType = this.nodeType;

		if ( nodeType === null ) {

			const attributeName = this.getAttributeName( builder );

			if ( builder.hasGeometryAttribute( attributeName ) ) {

				const attribute = builder.geometry.getAttribute( attributeName );

				nodeType = builder.getTypeFromAttribute( attribute );

			} else {

				nodeType = 'float';

			}

		}

		return nodeType;

	}

	/**
	 * Sets the attribute name to the given value. The method can be
	 * overwritten in derived classes if the final name must be computed
	 * analytically.
	 *
	 * @param {string} attributeName - The name of the attribute.
	 * @return {AttributeNode} A reference to this node.
	 */
	setAttributeName( attributeName ) {

		this._attributeName = attributeName;

		return this;

	}

	/**
	 * Returns the attribute name of this node. The method can be
	 * overwritten in derived classes if the final name must be computed
	 * analytically.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The attribute name.
	 */
	getAttributeName( /*builder*/ ) {

		return this._attributeName;

	}

	generate( builder ) {

		const attributeName = this.getAttributeName( builder );
		const nodeType = this.getNodeType( builder );
		const geometryAttribute = builder.hasGeometryAttribute( attributeName );

		if ( geometryAttribute === true ) {

			const attribute = builder.geometry.getAttribute( attributeName );
			const attributeType = builder.getTypeFromAttribute( attribute );

			const nodeAttribute = builder.getAttribute( attributeName, attributeType );

			if ( builder.shaderStage === 'vertex' ) {

				return builder.format( nodeAttribute.name, attributeType, nodeType );

			} else {

				const nodeVarying = varying( this );

				return nodeVarying.build( builder, nodeType );

			}

		} else {

			warn( `AttributeNode: Vertex attribute "${ attributeName }" not found on geometry.` );

			return builder.generateConst( nodeType );

		}

	}

	serialize( data ) {

		super.serialize( data );

		data.global = this.global;
		data._attributeName = this._attributeName;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.global = data.global;
		this._attributeName = data._attributeName;

	}

}

/**
 * TSL function for creating an attribute node.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the attribute.
 * @param {?string} [nodeType=null] - The node type.
 * @returns {AttributeNode}
 */
const attribute = ( name, nodeType = null ) => new AttributeNode( name, nodeType );

/**
 * A specialized context node designed to override specific target nodes within a
 * node sub-graph or flow. This allows replacing specific inputs (e.g., normal
 * and position vectors) dynamically during compilation for a specific flow node,
 * without having to reconstruct or duplicate the source nodes.
 *
 * ```js
 * // Method chaining example:
 * node.overrideNode( positionLocal, () => positionLocal.add( vec3( 1, 0, 0 ) ) );
 *
 * // Context assignment example:
 * material.contextNode = overrideNode( positionLocal, () => positionLocal.add( vec3( 1, 0, 0 ) ) );
 * ```
 *
 * @augments ContextNode
 */
class OverrideContextNode extends ContextNode {

	/**
	 * Returns the type of the node.
	 *
	 * @type {string}
	 * @readonly
	 * @static
	 */
	static get type() {

		return 'OverrideContextNode';

	}

	/**
	 * Constructs a new override context node.
	 *
	 * @param {Map<Node, Function>} overrideNodes - A map mapping target nodes to their respective override callback functions.
	 * @param {Node|null} [flowNode=null] - The node whose context should be modified.
	 * @param {?string} [shaderStage=null] - The shader stage the overrides apply to, all stages by default.
	 */
	constructor( overrideNodes, flowNode = null, shaderStage = null ) {

		const value = { overrideNodes };

		// the surface is replaced per fragment, the varyings are evaluated in the fragment stage as well

		if ( shaderStage === 'fragment' ) value.inlineVaryings = true;

		super( flowNode, value );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isOverrideContextNode = true;

	}

}

/**
 * TSL function for creating an `OverrideContextNode` to override a single target node.
 *
 * ```js
 * material.contextNode = overrideNode( positionLocal, ( builder ) => positionLocal.add( vec3( 1, 0, 0 ) ) );
 * ```
 *
 * @tsl
 * @function
 * @param {Node} targetNode - The target node that should be overridden.
 * @param {Function|Node|null} [callback=null] - A callback function returning the overriding node (which receives the builder as its argument), or the overriding node itself.
 * @param {Node|null} [flowNode=null] - The node whose context should be modified.
 * @return {OverrideContextNode} The created override context node.
 */
function overrideNode( targetNode, callback = null, flowNode = null ) {

	if ( callback && callback.isNode ) {

		const node = callback;

		callback = () => node;

	}

	return new OverrideContextNode( new Map( [[ targetNode, callback ]] ), flowNode );

}

addMethodChaining( 'overrideNode', ( flowNode, node, callback ) => overrideNode( node, callback, flowNode ) );

/**
 * TSL function for creating an `OverrideContextNode` to override multiple target nodes.
 *
 * ```js
 * material.contextNode = overrideNodes( [
 * 	[ positionView, customPositionView ],
 * 	[ positionViewDirection, ( builder ) => customPositionViewDirection ]
 * ] );
 * ```
 *
 * @tsl
 * @function
 * @param {Map<Node, (Function|Node)>|Array<Array<Node|Function|Node>>} overrides - The overrides mapping target nodes to callback functions or overriding nodes.
 * @param {Node|null} [flowNode=null] - The node whose context should be modified.
 * @param {?string} [shaderStage=null] - The shader stage the overrides apply to, all stages by default. With fragment overrides,
 * all varyings of the context are evaluated in the fragment stage, e.g. if the surface is reconstructed per fragment.
 * @return {OverrideContextNode} The created override context node.
 */
function overrideNodes( overrides, flowNode = null, shaderStage = null ) {

	const overrideNodesMap = new Map();

	for ( const [ node, value ] of overrides ) {

		let callback = null;

		if ( value !== null ) {

			callback = typeof value === 'function' ? value : () => value;

			if ( shaderStage !== null ) callback = getStageCallback( node, callback, shaderStage );

		}

		overrideNodesMap.set( node, callback );

	}

	return new OverrideContextNode( overrideNodesMap, flowNode, shaderStage );

}

addMethodChaining( 'overrideNodes', ( flowNode, overrides, shaderStage ) => overrideNodes( overrides, flowNode, shaderStage ) );

/**
 * Returns an override callback which only overrides the node in the given shader stage.
 *
 * @private
 * @param {Node} node - The target node.
 * @param {Function} callback - The override callback.
 * @param {string} shaderStage - The shader stage.
 * @return {Function} The override callback of the shader stage.
 */
function getStageCallback( node, callback, shaderStage ) {

	return ( builder ) => builder.shaderStage === shaderStage ? callback( builder ) : node;

}

/**
 * Special version of {@link PropertyNode} which is used for parameters.
 *
 * @augments PropertyNode
 */
class ParameterNode extends PropertyNode {

	static get type() {

		return 'ParameterNode';

	}

	/**
	 * Constructs a new parameter node.
	 *
	 * @param {string} nodeType - The type of the node.
	 * @param {?string} [name=null] - The name of the parameter in the shader.
	 */
	constructor( nodeType, name = null ) {

		super( nodeType, name );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isParameterNode = true;

	}

	/**
	 * Gets the type of a member variable in the parameter node.
	 *
	 * @param {NodeBuilder} builder - The node builder.
	 * @param {string} name - The name of the member variable.
	 * @returns {string}
	 */
	getMemberType( builder, name ) {

		const type = this.getNodeType( builder );
		const struct = builder.getStructTypeNode( type );

		let memberType;

		if ( struct !== null ) {

			memberType = struct.getMemberType( builder, name );

		} else {

			error( `TSL: Member "${ name }" not found in struct "${ type }".`, new StackTrace() );

			memberType = 'float';

		}

		return memberType;

	}

	getHash() {

		return String( this.id );

	}

	generate() {

		return this.name;

	}

}

/**
 * TSL function for creating a parameter node.
 *
 * @tsl
 * @function
 * @param {string} type - The type of the node.
 * @param {?string} name - The name of the parameter in the shader.
 * @returns {ParameterNode}
 */
const parameter = ( type, name ) => new ParameterNode( type, name );

/**
 * Whether the given node is assignable but has not been assigned. It is just
 * an expression, which is generated where it is used instead of in the stack.
 *
 * @private
 * @param {NodeBuilder} builder - The current node builder.
 * @param {Node} node - The node to check.
 * @return {boolean} Whether the assignable node can be generated as an expression.
 */
const isAssignableExpression = ( builder, node ) => {

	if ( node.assignable !== true ) return false;

	const data = builder.getDataFromNode( node );

	return data.forceDeclaration !== true && data.assign !== true;

};

/**
 * Stack is a helper for Nodes that need to produce stack-based code instead of continuous flow.
 * They are usually needed in cases like `If`, `Else`.
 *
 * @augments Node
 */
class StackNode extends Node {

	static get type() {

		return 'StackNode';

	}

	/**
	 * Constructs a new stack node.
	 *
	 * @param {?StackNode} [parent=null] - The parent stack node.
	 */
	constructor( parent = null ) {

		super();

		/**
		 * List of nodes.
		 *
		 * @type {Array<Node>}
		 */
		this.nodes = [];

		/**
		 * The output node.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.outputNode = null;

		/**
		 * The parent stack node.
		 *
		 * @type {?StackNode}
		 * @default null
		 */
		this.parent = parent;

		/**
		 * The current conditional node.
		 *
		 * @private
		 * @type {ConditionalNode}
		 * @default null
		 */
		this._currentCond = null;

		/**
		 * The expression node. Only
		 * relevant for Switch/Case.
		 *
		 * @private
		 * @type {Node}
		 * @default null
		 */
		this._expressionNode = null;

		/**
		 * The current node being processed.
		 *
		 * @private
		 * @type {Node}
		 * @default null
		 */
		this._currentNode = null;

		/**
		 * Stores additional data for nodes that are added to the stack.
		 *
		 * @private
		 * @type {Map<Node, {delta: number}>}
		 */
		this._nodeDataLibrary = new Map();

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isStackNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	getElementType( builder ) {

		return this.outputNode ? this.outputNode.getElementType( builder ) : 'void';

	}

	generateNodeType( builder ) {

		return this.outputNode ? this.outputNode.getNodeType( builder ) : 'void';

	}

	getMemberType( builder, name ) {

		return this.outputNode ? this.outputNode.getMemberType( builder, name ) : 'void';

	}

	/**
	 * Adds a node to this stack.
	 *
	 * @param {Node} node - The node to add.
	 * @param {number} [index=-1] - The index of the node. If not specified, the node will be added to the end of the stack.
	 * @return {StackNode} A reference to this stack node.
	 */
	addToStack( node, index = -1 ) {

		if ( node.isNode !== true ) {

			error( 'TSL: Invalid node added to stack.', new StackTrace() );
			return this;

		}


		if ( index === -1 ) {

			if ( this._currentNode ) {

				let nodeData = this._nodeDataLibrary.get( this._currentNode );

				if ( nodeData === undefined ) {

					nodeData = {
						delta: 0
					};

					this._nodeDataLibrary.set( this._currentNode, nodeData );

				}

				nodeData.delta ++;

				index = this.nodes.indexOf( this._currentNode ) + nodeData.delta;

			} else {

				index = this.nodes.length;

			}

		}

		this.nodes.splice( index, 0, node );

		return this;

	}

	/**
	 * Adds a node to the stack before the given node, by default the current node.
	 *
	 * @param {Node} node - The node to add.
	 * @param {?Node} [beforeNode=this._currentNode] - The node of the stack that the node is added before.
	 * @return {StackNode} A reference to this stack node.
	 */
	addToStackBefore( node, beforeNode = this._currentNode ) {

		const index = beforeNode ? this.nodes.indexOf( beforeNode ) : 0;

		return this.addToStack( node, index );

	}

	/**
	 * Represent an `if` statement in TSL.
	 *
	 * @param {Node} boolNode - Represents the condition.
	 * @param {Function} method - TSL code which is executed if the condition evaluates to `true`.
	 * @return {StackNode} A reference to this stack node.
	 */
	If( boolNode, method ) {

		const methodNode = new ShaderNode( method );
		this._currentCond = select( boolNode, methodNode );

		return this.addToStack( this._currentCond );

	}

	/**
	 * Represent an `elseif` statement in TSL.
	 *
	 * @param {Node} boolNode - Represents the condition.
	 * @param {Function} method - TSL code which is executed if the condition evaluates to `true`.
	 * @return {StackNode} A reference to this stack node.
	 */
	ElseIf( boolNode, method ) {

		const methodNode = new ShaderNode( method );
		const ifNode = select( boolNode, methodNode );

		this._currentCond.elseNode = ifNode;
		this._currentCond = ifNode;

		return this;

	}

	/**
	 * Represent an `else` statement in TSL.
	 *
	 * @param {Function} method - TSL code which is executed in the `else` case.
	 * @return {StackNode} A reference to this stack node.
	 */
	Else( method ) {

		this._currentCond.elseNode = new ShaderNode( method );

		return this;

	}

	/**
	 * Represents a `switch` statement in TSL.
	 *
	 * @param {any} expression - Represents the expression.
	 * @return {StackNode} A reference to this stack node.
	 */
	Switch( expression ) {

		this._expressionNode = nodeObject( expression );

		return this;

	}

	/**
	 * Represents a `case` statement in TSL. The TSL version accepts an arbitrary numbers of values.
	 * The last parameter must be the callback method that should be executed in the `true` case.
	 *
	 * @param {...any} params - The values of the `Case()` statement as well as the callback method.
	 * @return {StackNode} A reference to this stack node.
	 */
	Case( ...params ) {

		const caseNodes = [];

		// extract case nodes from the parameter list

		if ( params.length >= 2 ) {

			for ( let i = 0; i < params.length - 1; i ++ ) {

				caseNodes.push( this._expressionNode.equal( nodeObject( params[ i ] ) ) );

			}

		} else {

			error( 'TSL: Invalid parameter length. Case() requires at least two parameters.', new StackTrace() );

		}

		// extract method

		const method = params[ params.length - 1 ];
		const methodNode = new ShaderNode( method );

		// chain multiple cases when using Case( 1, 2, 3, () => {} )

		let caseNode = caseNodes[ 0 ];

		for ( let i = 1; i < caseNodes.length; i ++ ) {

			caseNode = caseNode.or( caseNodes[ i ] );

		}

		// build condition

		const condNode = select( caseNode, methodNode );

		if ( this._currentCond === null ) {

			this._currentCond = condNode;

			return this.addToStack( this._currentCond );

		} else {

			this._currentCond.elseNode = condNode;
			this._currentCond = condNode;

			return this;

		}

	}

	/**
	 * Represents the default code block of a Switch/Case statement.
	 *
	 * @param {Function} method - TSL code which is executed in the `else` case.
	 * @return {StackNode} A reference to this stack node.
	 */
	Default( method ) {

		this.Else( method );

		return this;

	}

	setup( builder ) {

		const nodeProperties = builder.getNodeProperties( this );

		let index = 0;

		for ( const childNode of this.getChildren() ) {

			if ( isAssignableExpression( builder, childNode ) ) continue;

			nodeProperties[ 'node' + index ++ ] = childNode;

		}

		// return a outputNode if exists or null

		return nodeProperties.outputNode || null;

	}

	build( builder, ...params ) {

		this._setupInspector( builder );

		const previousStack = getCurrentStack();

		const buildStage = builder.buildStage;

		setCurrentStack( this );

		builder.setActiveStack( this );

		//

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

			const node = this.nodes[ i ];
			const previousNode = this._currentNode;

			this._currentNode = node;

			if ( isAssignableExpression( builder, node ) ) continue;

			if ( buildStage === 'setup' ) {

				node.build( builder );

			} else if ( buildStage === 'analyze' ) {

				node.build( builder, this );

			} else if ( buildStage === 'generate' ) {

				const stages = builder.getDataFromNode( node, 'any' ).stages;
				const parents = stages && stages[ builder.shaderStage ];

				if ( node.isVarNode && parents && parents.length === 1 && parents[ 0 ] && parents[ 0 ].isStackNode ) {

					continue; // skip var nodes that are only used in .toVarying()

				}

				node.build( builder, 'void' );

			}

			this._currentNode = previousNode;

		}

		//

		let result;

		if ( this.outputNode ) {

			const buildResult = this.outputNode.build( builder, ...params );

			if ( builder.buildStage !== 'generate' || this.outputNode.getNodeType( builder ) !== 'void' ) {

				result = buildResult;

			}

		} else {

			result = super.build( builder, ...params );

		}

		setCurrentStack( previousStack );

		builder.removeActiveStack( this );

		return result;

	}

}

/**
 * TSL function for creating a stack node.
 *
 * @tsl
 * @function
 * @param {?StackNode} [parent=null] - The parent stack node.
 * @returns {StackNode}
 */
const stack = /*@__PURE__*/ nodeProxy( StackNode ).setParameterLength( 0, 1 );

/**
 * Generates a layout for struct members.
 * This function takes an object representing struct members and returns an array of member layouts.
 * Each member layout includes the member's name, type, and whether it is atomic.
 *
 * @param {Object.<string, string|Object>} members - An object where keys are member names and values are either types (as strings) or objects with type and atomic properties.
 * @returns {Array.<{name: string, type: string, atomic: boolean}>} An array of member layouts.
 */
function getMembersLayout( members ) {

	return Object.entries( members ).map( ( [ name, value ] ) => {

		if ( typeof value === 'string' ) {

			return { name, type: value, atomic: false };

		}

		return { name, type: value.type, atomic: value.atomic || false };

	} );

}

/**
 * Represents a struct type node in the node-based system.
 * This class is used to define and manage the layout and types of struct members.
 * It extends the base Node class and provides methods to get the length of the struct,
 * retrieve member types, and generate the struct type for a builder.
 *
 * @augments Node
 */
class StructTypeNode extends Node {

	static get type() {

		return 'StructTypeNode';

	}

	/**
	 * Creates an instance of StructTypeNode.
	 *
	 * @param {Object} membersLayout - The layout of the members for the struct.
	 * @param {?string} [name=null] - The optional name of the struct.
	 */
	constructor( membersLayout, name = null ) {

		super( 'struct' );

		/**
		 * The layout of the members for the struct
		 *
		 * @type {Array.<{name: string, type: string, atomic: boolean}>}
		 */
		this.membersLayout = getMembersLayout( membersLayout );

		/**
		 * The name of the struct.
		 *
		 * @type {?string}
		 * @default null
		 */
		this.name = name;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isStructTypeNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Returns the length of the struct in 4-byte elements (e.g. float or int components).
	 * The length is calculated by summing the lengths of the struct's members, accounting for memory alignment.
	 * To get the size in bytes, multiply the returned value by 4.
	 *
	 * @returns {number} The length of the struct in 4-byte elements.
	 */
	getLength() {

		let maxAlignment = 1; // maximum alignment value in this struct
		let offset = 0; // global buffer offset in 4 byte elements

		for ( const member of this.membersLayout ) {

			const type = member.type;

			const itemSize = getMemoryLengthFromType( type );
			const alignment = getAlignmentFromType( type );
			maxAlignment = Math.max( maxAlignment, alignment );

			const chunkOffset = offset % maxAlignment; // offset in the current chunk of maxAlignment elements
			const overhang = chunkOffset % alignment; // distance from the last aligned offset
			if ( overhang !== 0 ) {

				offset += alignment - overhang; // move to next aligned offset

			}

			offset += itemSize;

		}

		return ( Math.ceil( offset / maxAlignment ) * maxAlignment ); // ensure length is a multiple of maxAlignment

	}

	getMemberType( builder, name ) {

		const member = this.membersLayout.find( m => m.name === name );

		return member ? member.type : 'void';

	}

	generateNodeType( builder ) {

		const structType = builder.getStructTypeFromNode( this, this.membersLayout, this.name );

		return structType.name;

	}

	setup( builder ) {

		builder.getStructTypeFromNode( this, this.membersLayout, this.name );
		builder.addInclude( this );

	}

	generate( builder ) {

		return this.getNodeType( builder );

	}

}

/**
 * StructNode allows to create custom structures with multiple members.
 * This can also be used to define structures in attribute and uniform data.
 *
 * ```js
 * // Define a custom struct
 * const BoundingBox = struct( { min: 'vec3', max: 'vec3' } );
 *
 * // Create a new instance of the struct
 * const bb = BoundingBox( vec3( 0 ), vec3( 1 ) ); // style 1
 * const bb = BoundingBox( { min: vec3( 0 ), max: vec3( 1 ) } ); // style 2
 *
 * // Access the struct members
 * const min = bb.get( 'min' );
 *
 * // Assign a new value to a member
 * min.assign( vec3() );
 * ```
 * @augments Node
 */
class StructNode extends Node {

	static get type() {

		return 'StructNode';

	}

	constructor( structTypeNode, values ) {

		super( 'vec3' );

		this.structTypeNode = structTypeNode;
		this.values = values;

		this.isStructNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generateNodeType( builder ) {

		return this.structTypeNode.getNodeType( builder );

	}

	getMemberType( builder, name ) {

		return this.structTypeNode.getMemberType( builder, name );

	}

	_getChildren() {

		// Ensure struct type is the last child for correct code generation order

		const children = super._getChildren();

		const structTypeProperty = children.find( child => child.childNode === this.structTypeNode );

		children.splice( children.indexOf( structTypeProperty ), 1 );
		children.push( structTypeProperty );

		return children;

	}

	generate( builder ) {

		const nodeVar = builder.getVarFromNode( this );
		const structType = nodeVar.type;
		const propertyName = builder.getPropertyName( nodeVar );

		builder.addLineFlowCode( `${ propertyName } = ${ builder.generateStruct( structType, this.structTypeNode.membersLayout, this.values ) }`, this );

		return nodeVar.name;

	}

}

/**
 * TSL function for creating a struct node.
 *
 * @tsl
 * @function
 * @param {Object} membersLayout - The layout of the struct members.
 * @param {?string} [name=null] - The name of the struct.
 * @returns {Function} The struct function.
 */
const struct = ( membersLayout, name = null ) => {

	const structType = new StructTypeNode( membersLayout, name );

	const struct = ( ...params ) => {

		let values = null;

		if ( params.length > 0 ) {

			if ( isArrayAsParameter( params ) ) {

				values = {};

				const names = Object.keys( membersLayout );

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

					values[ names[ i ] ] = nodeObject( params[ i ] );

				}

			} else {

				values = params[ 0 ];

			}

		}

		return new StructNode( structType, values );

	};

	return nodeProxyConstructor( struct, structType );

};

/**
 * This node can be used to define multiple outputs in a shader programs.
 *
 * @augments Node
 */
class OutputStructNode extends Node {

	static get type() {

		return 'OutputStructNode';

	}

	/**
	 * Constructs a new output struct node. The constructor can be invoked with an
	 * arbitrary number of nodes representing the members.
	 *
	 * @param {...Node} members - A parameter list of nodes.
	 */
	constructor( ...members ) {

		super();

		/**
		 * An array of nodes which defines the output.
		 *
		 * @type {Array<Node>}
		 */
		this.members = members;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isOutputStructNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generateNodeType( /*builder*/ ) {

		return 'OutputType';

	}

	generate( builder ) {

		const nodeData = builder.getDataFromNode( this );

		if ( nodeData.membersLayout === undefined ) {

			const members = this.members;
			const membersLayout = [];

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

				const name = 'm' + i;
				const type = members[ i ].getNodeType( builder );

				membersLayout.push( { name, type, index: i } );

			}

			nodeData.membersLayout = membersLayout;
			nodeData.structType = builder.getOutputStructTypeFromNode( this, nodeData.membersLayout );

		}

		//

		const propertyName = builder.getOutputStructName();
		const members = this.members;

		const structPrefix = propertyName !== '' ? propertyName + '.' : '';

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

			const snippet = members[ i ].build( builder, nodeData.membersLayout[ i ].type );

			builder.addLineFlowCode( `${ structPrefix }m${ i } = ${ snippet }`, this );

		}

		return propertyName;

	}

}

/**
 * TSL function for creating an output struct node.
 *
 * @tsl
 * @function
 * @param {...Node} members - A parameter list of nodes.
 * @returns {OutputStructNode}
 */
const outputStruct = /*@__PURE__*/ nodeProxy( OutputStructNode );

/**
 * Represents blending configuration.
 *
 * This class encapsulates all blending-related properties that control how
 * a material's colors are combined with the colors already in the frame buffer.
 */
class BlendMode {

	/**
	 * Constructs a new blending configuration.
	 *
	 * @param {(NoBlending|NormalBlending|AdditiveBlending|SubtractiveBlending|MultiplyBlending|CustomBlending|MaterialBlending)} [blending=NormalBlending] - The blending mode.
	 */
	constructor( blending = NormalBlending ) {

		/**
		 * Defines the blending type.
		 *
		 * It must be set to `CustomBlending` if custom blending properties like
		 * {@link BlendMode#blendSrc}, {@link BlendMode#blendDst} or {@link BlendMode#blendEquation}
		 * should have any effect.
		 *
		 * @type {(NoBlending|NormalBlending|AdditiveBlending|SubtractiveBlending|MultiplyBlending|CustomBlending|MaterialBlending)}
		 * @default NormalBlending
		 */
		this.blending = blending;

		/**
		 * Defines the blending source factor.
		 *
		 * This determines how the source (incoming) fragment color is factored before being added
		 * to the destination (existing) fragment color in the frame buffer.
		 *
		 * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)}
		 * @default SrcAlphaFactor
		 */
		this.blendSrc = SrcAlphaFactor;

		/**
		 * Defines the blending destination factor.
		 *
		 * This determines how the destination (existing) fragment color in the frame buffer
		 * is factored before being combined with the source (incoming) fragment color.
		 *
		 * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)}
		 * @default OneMinusSrcAlphaFactor
		 */
		this.blendDst = OneMinusSrcAlphaFactor;

		/**
		 * Defines the blending equation.
		 *
		 * This determines how the source and destination colors are combined.
		 *
		 * @type {(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)}
		 * @default AddEquation
		 */
		this.blendEquation = AddEquation;

		/**
		 * Defines the blending source alpha factor.
		 *
		 * When set, this allows separate control of the alpha channel's source blending factor.
		 * If `null`, {@link BlendMode#blendSrc} is used for the alpha channel as well.
		 *
		 * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)}
		 * @default null
		 */
		this.blendSrcAlpha = null;

		/**
		 * Defines the blending destination alpha factor.
		 *
		 * When set, this allows separate control of the alpha channel's destination blending factor.
		 * If `null`, {@link BlendMode#blendDst} is used for the alpha channel as well.
		 *
		 * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)}
		 * @default null
		 */
		this.blendDstAlpha = null;

		/**
		 * Defines the blending equation of the alpha channel.
		 *
		 * When set, this allows separate control of the alpha channel's blending equation.
		 * If `null`, {@link BlendMode#blendEquation} is used for the alpha channel as well.
		 *
		 * @type {?(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)}
		 * @default null
		 */
		this.blendEquationAlpha = null;

		/**
		 * Defines whether to premultiply the alpha (transparency) value.
		 *
		 * If `true`, the RGB color of the texture or material is multiplied by its alpha value.
		 * This is useful for transparent textures/materials where the color data
		 * should already include the transparency information.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.premultiplyAlpha = false;

	}

	/**
	 * Copies the blending properties from the given source to this instance.
	 *
	 * @param {BlendMode} source - The blending configuration to copy from.
	 * @return {BlendMode} A reference to this instance.
	 */
	copy( source ) {

		this.blending = source.blending;
		this.blendSrc = source.blendSrc;
		this.blendDst = source.blendDst;
		this.blendEquation = source.blendEquation;
		this.blendSrcAlpha = source.blendSrcAlpha;
		this.blendDstAlpha = source.blendDstAlpha;
		this.blendEquationAlpha = source.blendEquationAlpha;
		this.premultiplyAlpha = source.premultiplyAlpha;

		return this;

	}

	/**
	 * Returns a clone of this blending configuration.
	 *
	 * @return {BlendMode} A new Blending instance with the same properties.
	 */
	clone() {

		return new this.constructor().copy( this );

	}

}

/**
 * A four-component version of {@link Color} which is internally
 * used by the renderer to represents clear color with alpha as
 * one object.
 *
 * @private
 * @augments Color
 */
class Color4 extends Color {

	/**
	 * Constructs a new four-component color.
	 * You can also pass a single THREE.Color, hex or
	 * string argument to this constructor.
	 *
	 * @param {number|string} [r=1] - The red value.
	 * @param {number} [g=1] - The green value.
	 * @param {number} [b=1] - The blue value.
	 * @param {number} [a=1] - The alpha value.
	 */
	constructor( r, g, b, a = 1 ) {

		super( r, g, b );

		this.a = a;

	}

	/**
	 * Overwrites the default to honor alpha.
	 * You can also pass a single THREE.Color, hex or
	 * string argument to this method.
	 *
	 * @param {number|string|Color} r - The red value.
	 * @param {number} [g] - The green value.
	 * @param {number} [b] - The blue value.
	 * @param {number} [a=1] - The alpha value.
	 * @return {Color4} A reference to this object.
	 */
	set( r, g, b, a = 1 ) {

		this.a = a;

		return super.set( r, g, b );

	}

	/**
	 * Overwrites the default to honor alpha.
	 *
	 * @param {Color4} color - The color to copy.
	 * @return {Color4} A reference to this object.
	 */
	copy( color ) {

		if ( color.a !== undefined ) this.a = color.a;

		return super.copy( color );

	}

	/**
	 * Overwrites the default to honor alpha.
	 *
	 * @return {Color4} The cloned color.
	 */
	clone() {

		return new this.constructor( this.r, this.g, this.b, this.a );

	}

	*[ Symbol.iterator ]() {

		yield this.r;
		yield this.g;
		yield this.b;
		yield this.a;

	}

}

// Predefined blend modes for MRT nodes.
const _noBlending = /**@__PURE__*/ new BlendMode( NoBlending );
const _materialBlending = /**@__PURE__*/ new BlendMode( MaterialBlending );

/**
 * Returns the MRT texture index for the given name.
 *
 * @param {Array<Texture>} textures - The textures of a MRT-configured render target.
 * @param {string} name - The name of the MRT texture which index is requested.
 * @return {number} The texture index.
 */
function getTextureIndex( textures, name ) {

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

		if ( textures[ i ].name === name ) {

			return i;

		}

	}

	return -1;

}

/**
 * This node can be used setup a MRT context for rendering. A typical MRT setup for
 * post-processing is shown below:
 * ```js
 * const mrtNode = mrt( {
 *   output: output,
 *   normal: normalView
 * } ) ;
 * ```
 * The MRT output is defined as a dictionary.
 *
 * @augments OutputStructNode
 */
class MRTNode extends OutputStructNode {

	static get type() {

		return 'MRTNode';

	}

	/**
	 * Constructs a new output struct node.
	 *
	 * @param {Object<string, Node>} outputNodes - The MRT outputs.
	 */
	constructor( outputNodes ) {

		super();

		/**
		 * A dictionary representing the MRT outputs. The key
		 * is the name of the output, the value the node which produces
		 * the output result.
		 *
		 * @type {Object<string, Node>}
		 */
		this.outputNodes = outputNodes;

		/**
		 * A dictionary storing the blend modes for each output.
		 *
		 * @type {Object<string, BlendMode>}
		 */
		this.blendModes = {
			output: _materialBlending
		};

		/**
		 * A dictionary storing the clear colors for each output.
		 *
		 * @type {Object<string, Color4>}
		 */
		this.clearColors = {};

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isMRTNode = true;

	}

	/**
	 * Sets the blend mode for the given output name.
	 *
	 * @param {string} name - The name of the output.
	 * @param {BlendMode} blend - The blending mode.
	 * @return {MRTNode} The current MRT node.
	 */
	setBlendMode( name, blend ) {

		this.blendModes[ name ] = blend;

		return this;

	}

	/**
	 * Returns the blend mode for the given output name.
	 *
	 * @param {string} name - The name of the output.
	 * @return {BlendMode} The blend mode.
	 */
	getBlendMode( name ) {

		return this.blendModes[ name ] || _noBlending;

	}

	/**
	 * Sets the clear color for the given output name.
	 *
	 * @param {string} name - The name of the output.
	 * @param {number|string|Color} color - The clear color.
	 * @param {number} [alpha=1] - The clear alpha.
	 * @return {MRTNode} The current MRT node.
	 */
	setClearColor( name, color, alpha = 1 ) {

		const clearColor = this.clearColors[ name ] || ( this.clearColors[ name ] = new Color4() );

		clearColor.set( color );
		clearColor.a = alpha;

		return this;

	}

	/**
	 * Returns the clear color for the given output name.
	 *
	 * @param {string} name - The name of the output.
	 * @return {?Color4} The clear color. Returns `null` if no clear color is defined
	 * which means the renderer's default clear policy is applied.
	 */
	getClearColor( name ) {

		return this.clearColors[ name ] || null;

	}

	/**
	 * Returns `true` if the MRT node has an output with the given name.
	 *
	 * @param {string} name - The name of the output.
	 * @return {NodeBuilder} Whether the MRT node has an output for the given name or not.
	 */
	has( name ) {

		return this.outputNodes[ name ] !== undefined;

	}

	/**
	 * Returns the output node for the given name.
	 *
	 * @param {string} name - The name of the output.
	 * @return {Node} The output node.
	 */
	get( name ) {

		return this.outputNodes[ name ];

	}

	/**
	 * Merges the outputs of the given MRT node with the outputs of this node.
	 *
	 * @param {MRTNode} mrtNode - The MRT to merge.
	 * @return {MRTNode} A new MRT node with merged outputs..
	 */
	merge( mrtNode ) {

		const outputs = { ...this.outputNodes, ...mrtNode.outputNodes };
		const blendModes = { ...this.blendModes, ...mrtNode.blendModes };
		const clearColors = { ...this.clearColors, ...mrtNode.clearColors };

		const mrtTarget = mrt( outputs );
		mrtTarget.blendModes = blendModes;
		mrtTarget.clearColors = clearColors;

		return mrtTarget;

	}

	setup( builder ) {

		const outputNodes = this.outputNodes;
		const mrt = builder.renderer.getRenderTarget();

		const members = [];

		const textures = mrt.textures;

		for ( const name in outputNodes ) {

			const index = getTextureIndex( textures, name );

			// Ignore if the output exists in the MRT but has never been used.
			if ( index === -1 ) continue;

			const type = builder.getOutputType( index );

			members[ index ] = outputNodes[ name ].convert( type );

		}

		this.members = members;

		return super.setup( builder );

	}

}

/**
 * TSL function for creating a MRT node.
 *
 * @tsl
 * @function
 * @param {Object<string, Node>} outputNodes - The MRT outputs.
 * @returns {MRTNode}
 */
const mrt = /*@__PURE__*/ nodeProxy( MRTNode );

/**
 * This node represents an operation that reinterprets the bit representation of a value
 * in one type as a value in another type.
 *
 * @augments Node
 */
class BitcastNode extends Node {

	static get type() {

		return 'BitcastNode';

	}

	/**
	 * Constructs a new bitcast node.
	 *
	 * @param {Node} valueNode - The value to convert.
	 * @param {string} conversionType - The type to convert to.
	 * @param {?string} [inputType = null] - The expected input data type of the bitcast operation.
	 */
	constructor( valueNode, conversionType, inputType = null ) {

		super();

		/**
		 * The data to bitcast to a new type.
		 *
		 * @type {Node}
		 */
		this.valueNode = valueNode;

		/**
		 * The type the value will be converted to.
		 *
		 * @type {string}
		 */
		this.conversionType = conversionType;


		/**
		 * The expected input data type of the bitcast operation.
		 *
		 *
		 * @type {string}
		 * @default null
		 */
		this.inputType = inputType;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isBitcastNode = true;

	}

	generateNodeType( builder ) {

		// GLSL aliasing
		if ( this.inputType !== null ) {

			const valueType = this.valueNode.getNodeType( builder );
			const valueLength = builder.getTypeLength( valueType );

			return builder.getTypeFromLength( valueLength, this.conversionType );

		}

		return this.conversionType;

	}


	generate( builder ) {

		const type = this.getNodeType( builder );
		let inputType = '';

		if ( this.inputType !== null ) {

			const valueType = this.valueNode.getNodeType( builder );
			const valueTypeLength = builder.getTypeLength( valueType );

			inputType = valueTypeLength === 1 ? this.inputType : builder.changeComponentType( valueType, this.inputType );

		} else {

			inputType = this.valueNode.getNodeType( builder );

		}

		return `${ builder.getBitcastMethod( type, inputType ) }( ${ this.valueNode.build( builder, inputType ) } )`;


	}

}

/**
 * Reinterpret the bit representation of a value in one type as a value in another type.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The parameter.
 * @param {string} y - The new type.
 * @returns {Node}
 */
const bitcast = /*@__PURE__*/ nodeProxyAssignable( BitcastNode ).setParameterLength( 2 );

/**
 * Bitcasts a float or a vector of floats to a corresponding integer type with the same element size.
 *
 * @tsl
 * @function
 * @param {Node<float>} value - The float or vector of floats to bitcast.
 * @returns {BitcastNode}
 */
const floatBitsToInt = ( value ) => new BitcastNode( value, 'int', 'float' );

/**
 * Bitcasts a float or a vector of floats to a corresponding unsigned integer type with the same element size.
 *
 * @tsl
 * @function
 * @param {Node<float>} value - The float or vector of floats to bitcast.
 * @returns {BitcastNode}
 */
const floatBitsToUint = ( value ) => new BitcastNode( value, 'uint', 'float' );

/**
 * Bitcasts an integer or a vector of integers to a corresponding float type with the same element size.
 *
 * @tsl
 * @function
 * @param {Node<int>} value - The integer or vector of integers to bitcast.
 * @returns {BitcastNode}
 */
const intBitsToFloat = ( value ) => new BitcastNode( value, 'float', 'int' );

/**
 * Bitcast an unsigned integer or a vector of unsigned integers to a corresponding float type with the same element size.
 *
 * @tsl
 * @function
 * @param {Node<uint>} value - The unsigned integer or vector of unsigned integers to bitcast.
 * @returns {BitcastNode}
 */
const uintBitsToFloat = ( value ) => new BitcastNode( value, 'float', 'uint' );

const registeredBitcountFunctions = {};

/**
 * This node represents an operation that counts the bits of a piece of shader data.
 *
 * @augments MathNode
 */
class BitcountNode extends MathNode {

	static get type() {

		return 'BitcountNode';

	}

	/**
	 * Constructs a new math node.
	 *
	 * @param {'countTrailingZeros'|'countLeadingZeros'|'countOneBits'} method - The method name.
	 * @param {Node} aNode - The first input.
	 */
	constructor( method, aNode ) {

		super( method, aNode );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isBitcountNode = true;

	}

	/**
	 * Casts the input value of the function to an integer if necessary.
	 *
	 * @private
	 * @param {Node<uint>|Node<int>} inputNode - The input value.
	 * @param {Node<uint>} outputNode - The output value.
	 * @param {string} elementType - The type of the input value.
	 */
	_resolveElementType( inputNode, outputNode, elementType ) {

		if ( elementType === 'int' ) {

			outputNode.assign( bitcast( inputNode, 'uint' ) );

		} else {

			outputNode.assign( inputNode );

		}

	}

	_returnDataNode( inputType ) {

		switch ( inputType ) {

			case 'uint': {

				return uint;

			}

			case 'int': {

				return int;

			}

			case 'uvec2': {

				return uvec2;

			}

			case 'uvec3': {

				return uvec3;

			}

			case 'uvec4': {

				return uvec4;

			}

			case 'ivec2': {

				return ivec2;

			}

			case 'ivec3': {

				return ivec3;

			}

			case 'ivec4': {

				return ivec4;

			}

		}

	}

	/**
	 * Creates and registers a reusable GLSL function that emulates the behavior of countTrailingZeros.
	 *
	 * @private
	 * @param {string} elementType - The type of the input value.
	 * @returns {Function} - The generated function
	 */
	_createTrailingZerosBaseLayout( elementType ) {

		const outputConvertNode = this._returnDataNode( elementType );

		const fnDef = Fn( ( [ value ] ) => {

			If( value.equal( uint( 0 ) ), () => {

				return outputConvertNode( 32 );

			} );

			const v = uint( 0.0 );

			this._resolveElementType( value, v, elementType );

			const f = float( v.bitAnd( negate( v ) ) );
			const uintBits = floatBitsToUint( f );

			const numTrailingZeros = ( uintBits.shiftRight( 23 ) ).sub( 127 );

			return outputConvertNode( numTrailingZeros );

		}, { value: elementType, return: elementType } );

		return fnDef;

	}

	/**
	 * Creates and registers a reusable GLSL function that emulates the behavior of countLeadingZeros.
	 *
	 * @private
	 * @param {string} elementType - The type of the input value.
	 * @returns {Function} - The generated function
	 */
	_createLeadingZerosBaseLayout( elementType ) {

		const outputConvertNode = this._returnDataNode( elementType );

		const fnDef = Fn( ( [ value ] ) => {

			If( value.equal( uint( 0 ) ), () => {

				return outputConvertNode( 32 );

			} );

			const v = uint( 0 );
			const n = uint( 0 );
			this._resolveElementType( value, v, elementType );

			If( v.shiftRight( 16 ).equal( 0 ), () => {

				n.addAssign( 16 );
				v.shiftLeftAssign( 16 );

			} );

			If( v.shiftRight( 24 ).equal( 0 ), () => {

				n.addAssign( 8 );
				v.shiftLeftAssign( 8 );

			} );

			If( v.shiftRight( 28 ).equal( 0 ), () => {

				n.addAssign( 4 );
				v.shiftLeftAssign( 4 );

			} );

			If( v.shiftRight( 30 ).equal( 0 ), () => {

				n.addAssign( 2 );
				v.shiftLeftAssign( 2 );

			} );

			If( v.shiftRight( 31 ).equal( 0 ), () => {

				n.addAssign( 1 );

			} );

			return outputConvertNode( n );

		}, { value: elementType, return: elementType } );

		return fnDef;

	}

	/**
	 * Creates and registers a reusable GLSL function that emulates the behavior of countOneBits.
	 *
	 * @private
	 * @param {string} elementType - The type of the input value.
	 * @returns {Function} - The generated function
	 */
	_createOneBitsBaseLayout( elementType ) {

		const outputConvertNode = this._returnDataNode( elementType );

		const fnDef = Fn( ( [ value ] ) => {

			const v = uint( 0.0 );

			this._resolveElementType( value, v, elementType );

			v.assign( v.sub( v.shiftRight( uint( 1 ) ).bitAnd( uint( 0x55555555 ) ) ) );
			v.assign( v.bitAnd( uint( 0x33333333 ) ).add( v.shiftRight( uint( 2 ) ).bitAnd( uint( 0x33333333 ) ) ) );

			const numBits = v.add( v.shiftRight( uint( 4 ) ) ).bitAnd( uint( 0xF0F0F0F ) ).mul( uint( 0x1010101 ) ).shiftRight( uint( 24 ) );

			return outputConvertNode( numBits );

		}, { value: elementType, return: elementType } );

		return fnDef;

	}

	/**
	 * Creates and registers a reusable GLSL function that emulates the behavior of the specified bitcount function.
	 * including considerations for component-wise bitcounts on vector type inputs.
	 *
	 * @private
	 * @param {string} inputType - The type of the input value.
	 * @param {number} typeLength - The vec length of the input value.
	 * @param {Function} baseFn - The base function that operates on an individual component of the vector.
	 * @returns {Function} - The alias function for the specified bitcount method.
	 */
	_createMainLayout( inputType, typeLength, baseFn ) {

		const outputConvertNode = this._returnDataNode( inputType );

		const fnDef = Fn( ( [ value ] ) => {

			if ( typeLength === 1 ) {

				return outputConvertNode( baseFn( value ) );

			} else {

				const vec = outputConvertNode( 0 );

				const components = [ 'x', 'y', 'z', 'w' ];
				for ( let i = 0; i < typeLength; i ++ ) {

					const component = components[ i ];

					vec[ component ].assign( baseFn( value[ component ] ) );

				}

				return vec;

			}

		}, { value: inputType, return: inputType } );

		return fnDef;

	}

	setup( builder ) {

		const { method, aNode } = this;

		const { renderer } = builder;

		if ( renderer.backend.isWebGPUBackend ) {

			// use built-in WGSL functions for WebGPU

			return super.setup( builder );

		}

		const inputType = this.getInputType( builder );
		const elementType = builder.getElementType( inputType );

		const typeLength = builder.getTypeLength( inputType );

		const baseMethod = `${method}_base_${elementType}`;
		const newMethod = `${method}_${inputType}`;

		let baseFn = registeredBitcountFunctions[ baseMethod ];

		if ( baseFn === undefined ) {

			switch ( method ) {

				case BitcountNode.COUNT_LEADING_ZEROS: {

					baseFn = this._createLeadingZerosBaseLayout( elementType );
					break;

				}

				case BitcountNode.COUNT_TRAILING_ZEROS: {

					baseFn = this._createTrailingZerosBaseLayout( elementType );
					break;

				}

				case BitcountNode.COUNT_ONE_BITS: {

					baseFn = this._createOneBitsBaseLayout( elementType );
					break;

				}

			}

			registeredBitcountFunctions[ baseMethod ] = baseFn;

		}

		let fn = registeredBitcountFunctions[ newMethod ];

		if ( fn === undefined ) {

			fn = this._createMainLayout( inputType, typeLength, baseFn );
			registeredBitcountFunctions[ newMethod ] = fn;

		}

		const output = Fn( () => {

			return fn(
				aNode,
			);

		} );

		return output();

	}

	static get COUNT_TRAILING_ZEROS() {

		return 'countTrailingZeros';

	}

	static get COUNT_LEADING_ZEROS() {

		return 'countLeadingZeros';

	}

	static get COUNT_ONE_BITS() {

		return 'countOneBits';

	}

}

/**
 * Finds the number of consecutive 0 bits from the least significant bit of the input value,
 * which is also the index of the least significant bit of the input value.
 *
 * Can only be used with {@link WebGPURenderer} and a WebGPU backend.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The input value.
 * @returns {Node}
 */
const countTrailingZeros = /*@__PURE__*/ nodeProxyAssignable( BitcountNode, BitcountNode.COUNT_TRAILING_ZEROS ).setParameterLength( 1 );

/**
 * Finds the number of consecutive 0 bits starting from the most significant bit of the input value.
 *
 * Can only be used with {@link WebGPURenderer} and a WebGPU backend.
 *
 * @tsl
 * @function
 * @param {Node | number} x - The input value.
 * @returns {Node}
 */
const countLeadingZeros = /*@__PURE__*/ nodeProxyAssignable( BitcountNode, BitcountNode.COUNT_LEADING_ZEROS ).setParameterLength( 1 );

/**
 * Finds the number of '1' bits set in the input value
 *
 * Can only be used with {@link WebGPURenderer} and a WebGPU backend.
 *
 * @tsl
 * @function
 * @returns {Node}
 */
const countOneBits = /*@__PURE__*/ nodeProxyAssignable( BitcountNode, BitcountNode.COUNT_ONE_BITS ).setParameterLength( 1 );

/**
 * Generates a hash value in the range `[0, 1]` from the given seed.
 *
 * @tsl
 * @function
 * @param {Node<float>} seed - The seed.
 * @return {Node<float>} The hash value.
 */
const hash = /*@__PURE__*/ Fn( ( [ seed ] ) => {

	// Taken from https://www.shadertoy.com/view/XlGcRh, originally from pcg-random.org

	const state = seed.toUint().mul( 747796405 ).add( 2891336453 );
	const word = state.shiftRight( state.shiftRight( 28 ).add( 4 ) ).bitXor( state ).mul( 277803737 );
	const result = word.shiftRight( 22 ).bitXor( word );

	return result.toFloat().mul( 1 / 2 ** 32 ); // Convert to range [0, 1)

}, { seed: 'uint', return: 'float' } );

/**
 * A function that remaps the `[0,1]` interval into the `[0,1]` interval.
 * The corners are mapped to `0` and the center to `1`.
 * Reference: {@link https://iquilezles.org/articles/functions/}.
 *
 * @tsl
 * @function
 * @param {Node<float>} x - The value to remap.
 * @param {Node<float>} k - Allows to control the remapping functions shape by rising the parabola to a power `k`.
 * @return {Node<float>} The remapped value.
 */
const parabola = ( x, k ) => pow( mul( 4.0, x.mul( sub( 1.0, x ) ) ), k );

/**
 * A function that remaps the `[0,1]` interval into the `[0,1]` interval.
 * Expands the sides and compresses the center, and keeps `0.5` mapped to `0.5`.
 * Reference: {@link https://iquilezles.org/articles/functions/}.
 *
 * @tsl
 * @function
 * @param {Node<float>} x - The value to remap.
 * @param {Node<float>} k - `k=1` is the identity curve,`k<1` produces the classic `gain()` shape, and `k>1` produces "s" shaped curves.
 * @return {Node<float>} The remapped value.
 */
const gain = ( x, k ) => select(
	x.lessThan( 0.5 ),
	pow( mul( 2.0, x ), k ).mul( 0.5 ),
	sub( 1.0, pow( mul( 2.0, sub( 1.0, x ) ), k ).mul( 0.5 ) )
);

/**
 * A function that remaps the `[0,1]` interval into the `[0,1]` interval.
 * A generalization of the `parabola()`. Keeps the corners mapped to 0 but allows the control of the shape one either side of the curve.
 * Reference: {@link https://iquilezles.org/articles/functions/}.
 *
 * @tsl
 * @function
 * @param {Node<float>} x - The value to remap.
 * @param {Node<float>} a - First control parameter.
 * @param {Node<float>} b - Second control parameter.
 * @return {Node<float>} The remapped value.
 */
const pcurve = ( x, a, b ) => pow( div( pow( x, a ), add( pow( x, a ), pow( sub( 1.0, x ), b ) ) ), div( 1.0, a ) );

/**
 * A phase shifted sinus curve that starts at zero and ends at zero, with bouncing behavior.
 * Reference: {@link https://iquilezles.org/articles/functions/}.
 *
 * @tsl
 * @function
 * @param {Node<float>} x - The value to compute the sin for.
 * @param {Node<float>} k - Controls the amount of bounces.
 * @return {Node<float>} The result value.
 */
const sinc = ( x, k ) => {

	const arg = abs( PI.mul( k.mul( x ).sub( 1.0 ) ) ).max( EPSILON ).toConst();

	return sin( arg ).div( arg );

};

/**
 * This node represents an operation that packs floating-point values of a vector into an unsigned 32-bit integer
 *
 * @augments Node
 */
class PackFloatNode extends Node {

	static get type() {

		return 'PackFloatNode';

	}

	/**
	 *
	 * @param {'snorm' | 'unorm' | 'float16'} encoding - The numeric encoding that describes how the float values are mapped to the integer range.
	 * @param {Node} vectorNode - The vector node to be packed
	 */
	constructor( encoding, vectorNode ) {

		super();

		/**
		 * The vector to be packed.
		 *
		 * @type {Node}
		 */
		this.vectorNode = vectorNode;

		/**
		 * The numeric encoding.
		 *
		 * @type {string}
		 */
		this.encoding = encoding;

		/**
		 * The component layout of the packed integer.
		 *
		 * @type {'2x16' | '4x8'}
		 * @default '2x16'
		 */
		this.layout = '2x16';

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isPackFloatNode = true;

	}

	generateNodeType() {

		return 'uint';

	}

	generate( builder ) {

		const inputType = this.vectorNode.getNodeType( builder );
		return `${ builder.getFloatPackingMethod( this.encoding, this.layout ) }(${ this.vectorNode.build( builder, inputType )})`;

	}

}

/**
 * Converts each component of the normalized float to 16-bit integer values. The results are packed into a single unsigned integer.
 * round(clamp(c, -1, +1) * 32767.0)
 *
 * @tsl
 * @function
 * @param {Node<vec2>} value - The 2-component vector to be packed
 * @returns {Node}
 */
const packSnorm2x16 = /*@__PURE__*/ nodeProxyAssignable( PackFloatNode, 'snorm' ).setParameterLength( 1 );

/**
 * Converts each component of the normalized float to 16-bit integer values. The results are packed into a single unsigned integer.
 * round(clamp(c, 0, +1) * 65535.0)
 *
 * @tsl
 * @function
 * @param {Node<vec2>} value - The 2-component vector to be packed
 * @returns {Node}
 */
const packUnorm2x16 = /*@__PURE__*/ nodeProxyAssignable( PackFloatNode, 'unorm' ).setParameterLength( 1 );

/**
 * Converts each component of the vec2 to 16-bit floating-point values. The results are packed into a single unsigned integer.
 *
 * @tsl
 * @function
 * @param {Node<vec2>} value - The 2-component vector to be packed
 * @returns {Node}
 */
const packHalf2x16 = /*@__PURE__*/ nodeProxyAssignable( PackFloatNode, 'float16' ).setParameterLength( 1 );

/**
 * Converts each component of the normalized float to 8-bit integer values. The results are packed into a single unsigned integer.
 *
 * @tsl
 * @function
 * @param {Node<vec4>} value - The 4-component vector to be packed
 * @returns {Node}
 */
const packSnorm4x8 = /*@__PURE__*/ nodeProxyAssignable( PackFloatNode, 'snorm', null, { layout: '4x8' } ).setParameterLength( 1 );

/**
 * Converts each component of the normalized float to 8-bit integer values. The results are packed into a single unsigned integer.
 *
 * @tsl
 * @function
 * @param {Node<vec4>} value - The 4-component vector to be packed
 * @returns {Node}
 */
const packUnorm4x8 = /*@__PURE__*/ nodeProxyAssignable( PackFloatNode, 'unorm', null, { layout: '4x8' } ).setParameterLength( 1 );

/**
 * Represents one of the built-in functions of WGSL's `packed_4x8_integer_dot_product`
 * language extension. If the extension is not available, the node falls back to an
 * emulation with plain integer bit operations.
 *
 * @augments Node
 */
class Packed4x8IntegerNode extends Node {

	static get type() {

		return 'Packed4x8IntegerNode';

	}

	/**
	 * Constructs a packed 4x8 integer function node.
	 *
	 * @param {string} method - The WGSL built-in function name.
	 * @param {Node} aNode - The first argument.
	 * @param {?Node} [bNode=null] - The optional second argument.
	 */
	constructor( method, aNode, bNode = null ) {

		super();

		/**
		 * The WGSL built-in function name.
		 *
		 * @type {string}
		 */
		this.method = method;

		/**
		 * The first argument.
		 *
		 * @type {Node}
		 */
		this.aNode = aNode;

		/**
		 * The optional second argument.
		 *
		 * @type {?Node}
		 */
		this.bNode = bNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isPacked4x8IntegerNode = true;

	}

	getInputType() {

		const method = this.method;

		if ( method === Packed4x8IntegerNode.PACK4X_I8 || method === Packed4x8IntegerNode.PACK4X_I8_CLAMP ) {

			return 'ivec4';

		} else if ( method === Packed4x8IntegerNode.PACK4X_U8 || method === Packed4x8IntegerNode.PACK4X_U8_CLAMP ) {

			return 'uvec4';

		}

		return 'uint';

	}

	generateNodeType() {

		const method = this.method;

		if ( method === Packed4x8IntegerNode.DOT4_I8_PACKED ) {

			return 'int';

		} else if ( method === Packed4x8IntegerNode.UNPACK4X_I8 ) {

			return 'ivec4';

		} else if ( method === Packed4x8IntegerNode.UNPACK4X_U8 ) {

			return 'uvec4';

		}

		return 'uint';

	}

	/**
	 * Returns the reusable `Fn()` definition that emulates this node's method.
	 *
	 * @private
	 * @returns {Function} The emulation function.
	 */
	_getEmulatedFn() {

		switch ( this.method ) {

			case Packed4x8IntegerNode.DOT4_U8_PACKED: return emulatedDot4U8Packed;
			case Packed4x8IntegerNode.DOT4_I8_PACKED: return emulatedDot4I8Packed;
			case Packed4x8IntegerNode.PACK4X_I8: return emulatedPack4xI8;
			case Packed4x8IntegerNode.PACK4X_U8: return emulatedPack4xU8;
			case Packed4x8IntegerNode.PACK4X_I8_CLAMP: return emulatedPack4xI8Clamp;
			case Packed4x8IntegerNode.PACK4X_U8_CLAMP: return emulatedPack4xU8Clamp;
			case Packed4x8IntegerNode.UNPACK4X_I8: return emulatedUnpack4xI8;
			case Packed4x8IntegerNode.UNPACK4X_U8: return emulatedUnpack4xU8;

		}

	}

	setup( builder ) {

		// check for native language support

		if ( builder.renderer.backend.isWebGPUBackend === true &&
			typeof navigator !== 'undefined' && navigator.gpu !== undefined &&
			navigator.gpu.wgslLanguageFeatures !== undefined && navigator.gpu.wgslLanguageFeatures.has( 'packed_4x8_integer_dot_product' )
		) {

			return super.setup( builder );

		}

		// emulation

		const { aNode, bNode } = this;
		const fn = this._getEmulatedFn();

		return bNode !== null ? fn( aNode, bNode ) : fn( aNode );

	}

	generate( builder, output ) {

		const properties = builder.getNodeProperties( this );

		if ( properties.outputNode ) {

			return super.generate( builder, output );

		}

		// generate native WGSL call

		const type = this.getNodeType( builder );
		const inputType = this.getInputType();
		const params = [ this.aNode.build( builder, inputType ) ];

		if ( this.bNode !== null ) params.push( this.bNode.build( builder, inputType ) );

		return builder.format( `${this.method}( ${params.join( ', ' )} )`, type, output );

	}

	serialize( data ) {

		super.serialize( data );

		data.method = this.method;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.method = data.method;

	}

	static get DOT4_U8_PACKED() {

		return 'dot4U8Packed';

	}

	static get DOT4_I8_PACKED() {

		return 'dot4I8Packed';

	}

	static get PACK4X_I8() {

		return 'pack4xI8';

	}

	static get PACK4X_U8() {

		return 'pack4xU8';

	}

	static get PACK4X_I8_CLAMP() {

		return 'pack4xI8Clamp';

	}

	static get PACK4X_U8_CLAMP() {

		return 'pack4xU8Clamp';

	}

	static get UNPACK4X_I8() {

		return 'unpack4xI8';

	}

	static get UNPACK4X_U8() {

		return 'unpack4xU8';

	}

}

// emulations

const emulatedPack4xU8 = /*@__PURE__*/ Fn( ( [ v ] ) => {

	const x = v.x.bitAnd( uint( 0xff ) );
	const y = v.y.bitAnd( uint( 0xff ) );
	const z = v.z.bitAnd( uint( 0xff ) );
	const w = v.w.bitAnd( uint( 0xff ) );

	return x.bitOr( y.shiftLeft( uint( 8 ) ) ).bitOr( z.shiftLeft( uint( 16 ) ) ).bitOr( w.shiftLeft( uint( 24 ) ) );

}, { v: 'uvec4', return: 'uint' } );

const emulatedPack4xI8 = /*@__PURE__*/ Fn( ( [ v ] ) => {

	return emulatedPack4xU8( uvec4( v ) );

}, { v: 'ivec4', return: 'uint' } );

const emulatedPack4xU8Clamp = /*@__PURE__*/ Fn( ( [ v ] ) => {

	return emulatedPack4xU8( clamp( v, uvec4( 0 ), uvec4( 255 ) ) );

}, { v: 'uvec4', return: 'uint' } );

const emulatedPack4xI8Clamp = /*@__PURE__*/ Fn( ( [ v ] ) => {

	return emulatedPack4xI8( clamp( v, ivec4( -128 ), ivec4( 127 ) ) );

}, { v: 'ivec4', return: 'uint' } );

const emulatedUnpack4xU8 = /*@__PURE__*/ Fn( ( [ v ] ) => {

	return uvec4(
		v.bitAnd( uint( 0xff ) ),
		v.shiftRight( uint( 8 ) ).bitAnd( uint( 0xff ) ),
		v.shiftRight( uint( 16 ) ).bitAnd( uint( 0xff ) ),
		v.shiftRight( uint( 24 ) ).bitAnd( uint( 0xff ) )
	);

}, { v: 'uint', return: 'uvec4' } );

function signExtendByte( v, byteShift ) {

	return int( v.shiftLeft( uint( 24 - byteShift ) ) ).shiftRight( int( 24 ) );

}

const emulatedUnpack4xI8 = /*@__PURE__*/ Fn( ( [ v ] ) => {

	return ivec4(
		signExtendByte( v, 0 ),
		signExtendByte( v, 8 ),
		signExtendByte( v, 16 ),
		signExtendByte( v, 24 )
	);

}, { v: 'uint', return: 'ivec4' } );

const emulatedDot4U8Packed = /*@__PURE__*/ Fn( ( [ a, b ] ) => {

	const ua = emulatedUnpack4xU8( a );
	const ub = emulatedUnpack4xU8( b );

	return ua.x.mul( ub.x ).add( ua.y.mul( ub.y ) ).add( ua.z.mul( ub.z ) ).add( ua.w.mul( ub.w ) );

}, { a: 'uint', b: 'uint', return: 'uint' } );

const emulatedDot4I8Packed = /*@__PURE__*/ Fn( ( [ a, b ] ) => {

	const ia = emulatedUnpack4xI8( a );
	const ib = emulatedUnpack4xI8( b );

	return ia.x.mul( ib.x ).add( ia.y.mul( ib.y ) ).add( ia.z.mul( ib.z ) ).add( ia.w.mul( ib.w ) );

}, { a: 'uint', b: 'uint', return: 'int' } );

/**
 * Computes the dot product of four unsigned 8-bit integer components packed
 * into each input.
 *
 * @tsl
 * @function
 * @param {Node<uint>} a - The first packed unsigned integer vector.
 * @param {Node<uint>} b - The second packed unsigned integer vector.
 * @returns {Node<uint>} The dot product.
 */
const dot4U8Packed = /*@__PURE__*/ nodeProxyAssignable( Packed4x8IntegerNode, Packed4x8IntegerNode.DOT4_U8_PACKED ).setParameterLength( 2 );

/**
 * Computes the dot product of four signed 8-bit integer components packed
 * into each input.
 *
 * @tsl
 * @function
 * @param {Node<uint>} a - The first packed signed integer vector.
 * @param {Node<uint>} b - The second packed signed integer vector.
 * @returns {Node<int>} The dot product.
 */
const dot4I8Packed = /*@__PURE__*/ nodeProxyAssignable( Packed4x8IntegerNode, Packed4x8IntegerNode.DOT4_I8_PACKED ).setParameterLength( 2 );

/**
 * Packs the least significant 8 bits of four signed integers into a `uint`.
 *
 * @tsl
 * @function
 * @param {Node<ivec4>} value - The signed integer vector to pack.
 * @returns {Node<uint>} The packed value.
 */
const pack4xI8 = /*@__PURE__*/ nodeProxyAssignable( Packed4x8IntegerNode, Packed4x8IntegerNode.PACK4X_I8 ).setParameterLength( 1 );

/**
 * Packs the least significant 8 bits of four unsigned integers into a `uint`.
 *
 * @tsl
 * @function
 * @param {Node<uvec4>} value - The unsigned integer vector to pack.
 * @returns {Node<uint>} The packed value.
 */
const pack4xU8 = /*@__PURE__*/ nodeProxyAssignable( Packed4x8IntegerNode, Packed4x8IntegerNode.PACK4X_U8 ).setParameterLength( 1 );

/**
 * Clamps four signed integers to the signed 8-bit range and packs them into a
 * `uint`.
 *
 * @tsl
 * @function
 * @param {Node<ivec4>} value - The signed integer vector to clamp and pack.
 * @returns {Node<uint>} The packed value.
 */
const pack4xI8Clamp = /*@__PURE__*/ nodeProxyAssignable( Packed4x8IntegerNode, Packed4x8IntegerNode.PACK4X_I8_CLAMP ).setParameterLength( 1 );

/**
 * Clamps four unsigned integers to the unsigned 8-bit range and packs them
 * into a `uint`.
 *
 * @tsl
 * @function
 * @param {Node<uvec4>} value - The unsigned integer vector to clamp and pack.
 * @returns {Node<uint>} The packed value.
 */
const pack4xU8Clamp = /*@__PURE__*/ nodeProxyAssignable( Packed4x8IntegerNode, Packed4x8IntegerNode.PACK4X_U8_CLAMP ).setParameterLength( 1 );

/**
 * Unpacks a `uint` into four sign-extended signed 8-bit integer components.
 *
 * @tsl
 * @function
 * @param {Node<uint>} value - The packed value.
 * @returns {Node<ivec4>} The unpacked signed integer vector.
 */
const unpack4xI8 = /*@__PURE__*/ nodeProxyAssignable( Packed4x8IntegerNode, Packed4x8IntegerNode.UNPACK4X_I8 ).setParameterLength( 1 );

/**
 * Unpacks a `uint` into four zero-extended unsigned 8-bit integer components.
 *
 * @tsl
 * @function
 * @param {Node<uint>} value - The packed value.
 * @returns {Node<uvec4>} The unpacked unsigned integer vector.
 */
const unpack4xU8 = /*@__PURE__*/ nodeProxyAssignable( Packed4x8IntegerNode, Packed4x8IntegerNode.UNPACK4X_U8 ).setParameterLength( 1 );

/**
 * This node represents an operation that unpacks values from a 32-bit unsigned integer, reinterpreting the results as a floating-point vector
 *
 * @augments Node
 */
class UnpackFloatNode extends Node {

	static get type() {

		return 'UnpackFloatNode';

	}

	/**
	 *
	 * @param {'snorm' | 'unorm' | 'float16'} encoding - The numeric encoding that describes how the integer values are mapped to the float range
	 * @param {Node} uintNode - The uint node to be unpacked
	 */
	constructor( encoding, uintNode ) {

		super();

		/**
		 * The unsigned integer to be unpacked.
		 *
		 * @type {Node}
		 */
		this.uintNode = uintNode;

		/**
		 * The numeric encoding.
		 *
		 * @type {string}
		 */
		this.encoding = encoding;

		/**
		 * The component layout of the packed integer.
		 *
		 * @type {'2x16' | '4x8'}
		 * @default '2x16'
		 */
		this.layout = '2x16';

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isUnpackFloatNode = true;

	}

	generateNodeType() {

		return this.layout === '4x8' ? 'vec4' : 'vec2';

	}

	generate( builder ) {

		const inputType = this.uintNode.getNodeType( builder );
		return `${ builder.getFloatUnpackingMethod( this.encoding, this.layout ) }(${ this.uintNode.build( builder, inputType )})`;

	}

}

/**
 * Unpacks a 32-bit unsigned integer into two 16-bit values, interpreted as normalized signed integers. Returns a vec2 with both values.
 *
 * @tsl
 * @function
 * @param {Node<uint>} value - The unsigned integer to be unpacked
 * @returns {Node}
 */
const unpackSnorm2x16 = /*@__PURE__*/ nodeProxyAssignable( UnpackFloatNode, 'snorm' ).setParameterLength( 1 );

/**
 * Unpacks a 32-bit unsigned integer into two 16-bit values, interpreted as normalized unsigned integers. Returns a vec2 with both values.
 *
 * @tsl
 * @function
 * @param {Node<uint>} value - The unsigned integer to be unpacked
 * @returns {Node}
 */
const unpackUnorm2x16 = /*@__PURE__*/ nodeProxyAssignable( UnpackFloatNode, 'unorm' ).setParameterLength( 1 );

/**
 * Unpacks a 32-bit unsigned integer into two 16-bit values, interpreted as 16-bit floating-point numbers. Returns a vec2 with both values.
 *
 * @tsl
 * @function
 * @param {Node<uint>} value - The unsigned integer to be unpacked
 * @returns {Node}
 */
const unpackHalf2x16 = /*@__PURE__*/ nodeProxyAssignable( UnpackFloatNode, 'float16' ).setParameterLength( 1 );

/**
 * Unpacks a 32-bit unsigned integer into four 8-bit values, interpreted as normalized signed integers. Returns a vec4 with all values.
 *
 * @tsl
 * @function
 * @param {Node<uint>} value - The unsigned integer to be unpacked
 * @returns {Node}
 */
const unpackSnorm4x8 = /*@__PURE__*/ nodeProxyAssignable( UnpackFloatNode, 'snorm', null, { layout: '4x8' } ).setParameterLength( 1 );

/**
 * Unpacks a 32-bit unsigned integer into four 8-bit values, interpreted as normalized unsigned integers. Returns a vec4 with all values.
 *
 * @tsl
 * @function
 * @param {Node<uint>} value - The unsigned integer to be unpacked
 * @returns {Node}
 */
const unpackUnorm4x8 = /*@__PURE__*/ nodeProxyAssignable( UnpackFloatNode, 'unorm', null, { layout: '4x8' } ).setParameterLength( 1 );

/**
 * This module offers a variety of ways to implement loops in TSL. In it's basic form it's:
 * ```js
 * Loop( count, ( { i } ) => {
 *
 * } );
 * ```
 * However, it is also possible to define a start and end ranges, data types and loop conditions:
 * ```js
 * Loop( { start: int( 0 ), end: int( 10 ), type: 'int', condition: '<' }, ( { i } ) => {
 *
 * } );
 *```
 * Nested loops can be defined in a compacted form:
 * ```js
 * Loop( 10, 5, ( { i, j } ) => {
 *
 * } );
 * ```
 * Loops that should run backwards can be defined like so:
 * ```js
 * Loop( { start: 10 }, () => {} );
 * ```
 * It is possible to execute with boolean values, similar to the `while` syntax.
 * ```js
 * const value = float( 0 ).toVar();
 *
 * Loop( value.lessThan( 10 ), () => {
 *
 * 	value.addAssign( 1 );
 *
 * } );
 * ```
 * The module also provides `Break()` and `Continue()` TSL expressions for loop control.
 * @augments Node
 */
class LoopNode extends Node {

	static get type() {

		return 'LoopNode';

	}

	/**
	 * Constructs a new loop node.
	 *
	 * @param {Array<LoopNode~Params|loopBodyCallback>} params - Any number of loop parameters followed by the loop body.
	 */
	constructor( params = [] ) {

		super( 'void' );

		this.params = params;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Returns a loop variable name based on an index. The pattern is
	 * `0` = `i`, `1`= `j`, `2`= `k` and so on.
	 *
	 * @param {number} index - The index.
	 * @return {string} The loop variable name.
	 */
	getVarName( index ) {

		return String.fromCharCode( 'i'.charCodeAt( 0 ) + index );

	}

	/**
	 * Returns properties about this node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Object} The node properties.
	 */
	getProperties( builder ) {

		const properties = builder.getNodeProperties( this );

		if ( properties.stackNode !== undefined ) return properties;

		//

		const inputs = {};
		const params = this._getInternalParams();

		for ( let i = 0, l = params.length - 1; i < l; i ++ ) {

			const param = params[ i ];

			const name = ( param.isNode !== true && param.name ) || this.getVarName( i );
			const type = ( param.isNode !== true && param.type ) || 'int';

			inputs[ name ] = expression( name, type );

		}

		const stack = builder.addStack();

		const fnCall = params[ params.length - 1 ]( inputs );

		properties.returnsNode = fnCall.context( { nodeLoop: this, nodeBlock: builder.createBlock() } );
		properties.stackNode = stack;

		const baseParam = params[ 0 ];

		if ( baseParam.isNode !== true && typeof baseParam.update === 'function' ) {

			const fnUpdateCall = Fn( baseParam.update )( inputs );

			properties.updateNode = fnUpdateCall.context( { nodeLoop: this } );

		}

		builder.removeStack();

		return properties;

	}

	_getInternalParams() {

		const params = this.params;

		if ( typeof params[ 0 ] === 'function' ) {

			return [ bool( true ), params[ 0 ] ];

		}

		return params;

	}

	setup( builder ) {

		// setup properties

		this.getProperties( builder );

	}

	generate( builder ) {

		const properties = this.getProperties( builder );

		const params = this._getInternalParams();
		const stackNode = properties.stackNode;

		for ( let i = 0, l = params.length - 1; i < l; i ++ ) {

			const param = params[ i ];

			let isWhile = false, start = null, end = null, name = null, type = null, condition = null, update = null;

			if ( param.isNode ) {

				if ( param.getNodeType( builder ) === 'bool' ) {

					isWhile = true;
					type = 'bool';
					end = param.build( builder, type );

				} else {

					type = 'int';
					name = this.getVarName( i );
					start = '0';
					end = param.build( builder, type );
					condition = '<';

				}

			} else {

				type = param.type || 'int';
				name = param.name || this.getVarName( i );
				start = param.start;
				end = param.end;
				condition = param.condition;
				update = param.update;

				if ( typeof start === 'number' ) start = builder.generateConst( type, start );
				else if ( start && start.isNode ) start = start.build( builder, type );

				if ( typeof end === 'number' ) end = builder.generateConst( type, end );
				else if ( end && end.isNode ) end = end.build( builder, type );

				if ( start !== undefined && end === undefined ) {

					start = start + ' - 1';
					end = '0';
					condition = '>=';

				} else if ( end !== undefined && start === undefined ) {

					start = '0';
					condition = '<';

				}

				if ( condition === undefined ) {

					if ( Number( start ) > Number( end ) ) {

						condition = '>=';

					} else {

						condition = '<';

					}

				}

			}

			let loopSnippet;

			if ( isWhile ) {

				loopSnippet = `while ( ${ end } )`;

			} else {

				const internalParam = { start, end};

				//

				const startSnippet = internalParam.start;
				const endSnippet = internalParam.end;

				let updateSnippet;

				const deltaOperator = () => condition.includes( '<' ) ? '+=' : '-=';

				if ( update !== undefined && update !== null ) {

					switch ( typeof update ) {

						case 'function':

							const flow = builder.flowStagesNode( properties.updateNode, 'void' );
							const snippet = flow.code.replace( /\t|;/g, '' );

							updateSnippet = snippet;

							break;

						case 'number':

							updateSnippet = name + ' ' + deltaOperator() + ' ' + builder.generateConst( type, update );

							break;

						case 'string':

							updateSnippet = name + ' ' + update;

							break;

						default:

							if ( update.isNode ) {

								updateSnippet = name + ' ' + deltaOperator() + ' ' + update.build( builder );

							} else {

								error( 'TSL: \'Loop( { update: ... } )\' is not a function, string or number.', this.stackTrace );

								updateSnippet = 'break /* invalid update */';

							}

					}

				} else {

					if ( type === 'int' || type === 'uint' ) {

						update = condition.includes( '<' ) ? '++' : '--';

					} else {

						update = deltaOperator() + ' 1.';

					}

					updateSnippet = name + ' ' + update;

				}

				const declarationSnippet = builder.getVar( type, name ) + ' = ' + startSnippet;
				const conditionalSnippet = name + ' ' + condition + ' ' + endSnippet;

				loopSnippet = `for ( ${ declarationSnippet }; ${ conditionalSnippet }; ${ updateSnippet } )`;

			}

			builder.addFlowCode( ( i === 0 ? '\n' : '' ) + builder.tab + loopSnippet + ' {\n\n' ).addFlowTab();

		}

		const flowBlock = builder.flowBlock;

		builder.flowBlock = { parent: flowBlock };

		const stackSnippet = stackNode.build( builder, 'void' );

		properties.returnsNode.build( builder, 'void' );

		builder.flowBlock = flowBlock;

		builder.removeFlowTab().addFlowCode( '\n' + builder.tab + stackSnippet );

		for ( let i = 0, l = params.length - 1; i < l; i ++ ) {

			builder.addFlowCode( ( i === 0 ? '' : builder.tab ) + '}\n\n' ).removeFlowTab();

		}

		builder.addFlowTab();

	}

}

/**
 * TSL function for creating a loop node.
 *
 * @tsl
 * @function
 * @param {...(LoopNode~Params|loopBodyCallback)} params - Any number of loop parameters followed by the loop body.
 * @returns {LoopNode}
 */
const Loop = ( ...params ) => new LoopNode( nodeArray( params, 'int' ) ).toStack();

/**
 * TSL function for inserting a `continue` expression into the shader.
 *
 * @tsl
 * @function
 * @returns {ExpressionNode}
 */
const Continue = () => expression( 'continue' ).toStack();

/**
 * TSL function for inserting a `break` expression into the shader.
 *
 * @tsl
 * @function
 * @returns {ExpressionNode}
 */
const Break = () => expression( 'break' ).toStack();

/**
 * The parameters of a loop. A number or int/uint node defines the loop's end value,
 * a bool node defines a `while` loop and an object allows a more detailed configuration.
 *
 * @typedef {number|Node<int>|Node<uint>|Node<bool>|LoopNode~ObjectParams} LoopNode~Params
 */

/**
 * A detailed loop configuration.
 *
 * @typedef {Object} LoopNode~ObjectParams
 * @property {number|Node<int>|Node<uint>} [start=0] - The initial value of the loop variable.
 * @property {number|Node<int>|Node<uint>} [end] - The value the loop variable is compared against. If omitted, the loop counts down from `start - 1` to `0`.
 * @property {string} [name] - The name of the loop variable. Defaults to `i`, `j`, `k` and so on.
 * @property {string} [type='int'] - The data type of the loop variable.
 * @property {('<'|'<='|'>'|'>=')} [condition] - The comparison operator. The loop runs as long as the comparison is true. Inferred from `start` and `end` if not set.
 * @property {string|number|Function|Node} [update] - Defines how the loop variable is updated after each iteration. Inferred from `condition` and `type` if not set.
 */

/**
 * The loop body.
 *
 * @callback loopBodyCallback
 * @param {Object<string, Node>} inputs - The loop variables of the current `Loop()` call, keyed by their name.
 */

// https://github.com/cabbibo/glsl-tri-noise-3d


const tri = /*@__PURE__*/ Fn( ( [ x ] ) => {

	return x.fract().sub( .5 ).abs();

}, { x: 'float', return: 'float' } );

const tri3 = /*@__PURE__*/ Fn( ( [ p ] ) => {

	return vec3( tri( p.z.add( tri( p.y.mul( 1. ) ) ) ), tri( p.z.add( tri( p.x.mul( 1. ) ) ) ), tri( p.y.add( tri( p.x.mul( 1. ) ) ) ) );

}, { p: 'vec3', return: 'vec3' } );

/**
 * Generates a noise value from the given position, speed and time parameters.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} position - The position.
 * @param {Node<float>} speed - The speed.
 * @param {Node<float>} time - The time.
 * @return {Node<float>} The generated noise.
 */
const triNoise3D = /*@__PURE__*/ Fn( ( [ position, speed, time ] ) => {

	const p = vec3( position ).toVar();
	const z = float( 1.4 ).toVar();
	const rz = float( 0.0 ).toVar();
	const bp = vec3( p ).toVar();

	Loop( { start: float( 0.0 ), end: float( 3.0 ), type: 'float', condition: '<=' }, () => {

		const dg = vec3( tri3( bp.mul( 2.0 ) ) ).toVar();
		p.addAssign( dg.add( time.mul( float( 0.1 ).mul( speed ) ) ) );
		bp.mulAssign( 1.8 );
		z.mulAssign( 1.5 );
		p.mulAssign( 1.2 );

		const t = float( tri( p.z.add( tri( p.x.add( tri( p.y ) ) ) ) ) ).toVar();
		rz.addAssign( t.div( z ) );
		bp.addAssign( 0.14 );

	} );

	return rz;

}, { position: 'vec3', speed: 'float', time: 'float', return: 'float' } );

const _sphere = /*@__PURE__*/ new Sphere();

/**
 * This node can be used to access transformation related metrics of 3D objects.
 * Depending on the selected scope, a different metric is represented as a uniform
 * in the shader. The following scopes are supported:
 *
 * - `POSITION`: The object's position in world space.
 * - `VIEW_POSITION`: The object's position in view/camera space.
 * - `DIRECTION`: The object's direction in world space.
 * - `SCALE`: The object's scale in world space.
 * - `WORLD_MATRIX`: The object's matrix in world space.
 *
 * @augments Node
 */
class Object3DNode extends Node {

	static get type() {

		return 'Object3DNode';

	}

	/**
	 * Constructs a new object 3D node.
	 *
	 * @param {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} scope - The node represents a different type of transformation depending on the scope.
	 * @param {?Object3D} [object3d=null] - The 3D object.
	 */
	constructor( scope, object3d = null ) {

		super();

		/**
		 * The node reports a different type of transformation depending on the scope.
		 *
		 * @type {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')}
		 */
		this.scope = scope;

		/**
		 * The 3D object.
		 *
		 * @type {?Object3D}
		 * @default null
		 */
		this.object3d = object3d;

		/**
		 * Overwritten since this type of node is updated per object.
		 *
		 * @type {string}
		 * @default 'object'
		 */
		this.updateType = NodeUpdateType.OBJECT;

		/**
		 * Holds the value of the node as a uniform.
		 *
		 * @type {UniformNode}
		 */
		this.uniformNode = new UniformNode( null );

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Overwritten since the node type is inferred from the scope.
	 *
	 * @return {('mat4'|'vec3'|'float')} The node type.
	 */
	generateNodeType() {

		const scope = this.scope;

		if ( scope === Object3DNode.WORLD_MATRIX ) {

			return 'mat4';

		} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) {

			return 'vec3';

		} else if ( scope === Object3DNode.RADIUS ) {

			return 'float';

		}

	}

	/**
	 * Updates the uniform value depending on the scope.
	 *
	 * @param {NodeFrame} frame - The current node frame.
	 */
	update( frame ) {

		const object = this.object3d;
		const uniformNode = this.uniformNode;
		const scope = this.scope;

		if ( scope === Object3DNode.WORLD_MATRIX ) {

			uniformNode.value = object.matrixWorld;

		} else if ( scope === Object3DNode.POSITION ) {

			uniformNode.value = uniformNode.value || new Vector3();

			uniformNode.value.setFromMatrixPosition( object.matrixWorld );

		} else if ( scope === Object3DNode.SCALE ) {

			uniformNode.value = uniformNode.value || new Vector3();

			uniformNode.value.setFromMatrixScale( object.matrixWorld );

		} else if ( scope === Object3DNode.DIRECTION ) {

			uniformNode.value = uniformNode.value || new Vector3();

			object.getWorldDirection( uniformNode.value );

		} else if ( scope === Object3DNode.VIEW_POSITION ) {

			const camera = frame.camera;

			uniformNode.value = uniformNode.value || new Vector3();
			uniformNode.value.setFromMatrixPosition( object.matrixWorld );

			uniformNode.value.applyMatrix4( camera.matrixWorldInverse );

		} else if ( scope === Object3DNode.RADIUS ) {

			const geometry = frame.object.geometry;

			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

			_sphere.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld );

			uniformNode.value = _sphere.radius;

		}

	}

	/**
	 * Generates the code snippet of the uniform node. The node type of the uniform
	 * node also depends on the selected scope.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The generated code snippet.
	 */
	generate( builder ) {

		const scope = this.scope;

		if ( scope === Object3DNode.WORLD_MATRIX ) {

			this.uniformNode.nodeType = 'mat4';

		} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) {

			this.uniformNode.nodeType = 'vec3';

		} else if ( scope === Object3DNode.RADIUS ) {

			this.uniformNode.nodeType = 'float';

		}

		return this.uniformNode.build( builder );

	}

	serialize( data ) {

		super.serialize( data );

		data.scope = this.scope;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.scope = data.scope;

	}

}

Object3DNode.WORLD_MATRIX = 'worldMatrix';
Object3DNode.POSITION = 'position';
Object3DNode.SCALE = 'scale';
Object3DNode.VIEW_POSITION = 'viewPosition';
Object3DNode.DIRECTION = 'direction';
Object3DNode.RADIUS = 'radius';

/**
 * TSL function for creating an object 3D node that represents the object's direction in world space.
 *
 * @tsl
 * @function
 * @param {?Object3D} [object3d] - The 3D object.
 * @returns {Object3DNode<vec3>}
 */
const objectDirection = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.DIRECTION ).setParameterLength( 1 );

/**
 * TSL function for creating an object 3D node that represents the object's world matrix.
 *
 * @tsl
 * @function
 * @param {?Object3D} [object3d] - The 3D object.
 * @returns {Object3DNode<mat4>}
 */
const objectWorldMatrix = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.WORLD_MATRIX ).setParameterLength( 1 );

/**
 * TSL function for creating an object 3D node that represents the object's position in world space.
 *
 * @tsl
 * @function
 * @param {?Object3D} [object3d] - The 3D object.
 * @returns {Object3DNode<vec3>}
 */
const objectPosition = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.POSITION ).setParameterLength( 1 );

/**
 * TSL function for creating an object 3D node that represents the object's scale in world space.
 *
 * @tsl
 * @function
 * @param {?Object3D} [object3d] - The 3D object.
 * @returns {Object3DNode<vec3>}
 */
const objectScale = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.SCALE ).setParameterLength( 1 );

/**
 * TSL function for creating an object 3D node that represents the object's position in view/camera space.
 *
 * @tsl
 * @function
 * @param {?Object3D} [object3d] - The 3D object.
 * @returns {Object3DNode<vec3>}
 */
const objectViewPosition = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.VIEW_POSITION ).setParameterLength( 1 );

/**
 * TSL function for creating an object 3D node that represents the object's radius.
 *
 * @tsl
 * @function
 * @param {?Object3D} [object3d] - The 3D object.
 * @returns {Object3DNode<float>}
 */
const objectRadius = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.RADIUS ).setParameterLength( 1 );

/**
 * The node allows to set values for built-in shader variables. That is
 * required for features like hardware-accelerated vertex clipping.
 *
 * @augments Node
 */
class BuiltinNode extends Node {

	/**
	 * Constructs a new builtin node.
	 *
	 * @param {string} name - The name of the built-in shader variable.
	 */
	constructor( name ) {

		super( 'float' );

		/**
		 * The name of the built-in shader variable.
		 *
		 * @type {string}
		 */
		this.name = name;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isBuiltinNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Generates the code snippet of the builtin node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The generated code snippet.
	 */
	generate( /* builder */ ) {

		return this.name;

	}

}

/**
 * TSL function for creating a builtin node.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the built-in shader variable.
 * @returns {BuiltinNode}
 */
const builtin = /*@__PURE__*/ nodeProxy( BuiltinNode ).setParameterLength( 1 );

/**
 * A special type of uniform node which represents array-like data
 * as uniform buffers. The access usually happens via `element()`
 * which returns an instance of {@link ArrayElementNode}. For example:
 *
 * ```js
 * const bufferNode = buffer( array, 'mat4', count );
 * const matrixNode = bufferNode.element( index ); // access a matrix from the buffer
 * ```
 * In general, it is recommended to use the more managed {@link UniformArrayNode}
 * since it handles more input types and automatically cares about buffer paddings.
 *
 * @augments UniformNode
 */
class BufferNode extends UniformNode {

	static get type() {

		return 'BufferNode';

	}

	/**
	 * Constructs a new buffer node.
	 *
	 * @param {Array<number>} value - Array-like buffer data.
	 * @param {string} bufferType - The data type of the buffer.
	 * @param {number} [bufferCount=0] - The count of buffer elements.
	 */
	constructor( value, bufferType, bufferCount = 0 ) {

		super( value, bufferType );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isBufferNode = true;

		/**
		 * The data type of the buffer.
		 *
		 * @type {string}
		 */
		this.bufferType = bufferType;

		/**
		 * The uniform node that holds the value of the reference node.
		 *
		 * @type {number}
		 * @default 0
		 */
		this.bufferCount = bufferCount;

		/**
		 * An array of update ranges.
		 *
		 * @type {Array<{start: number, count: number}>}
		 */
		this.updateRanges = [];

	}

	/**
	 * Adds a range of data in the data array to be updated on the GPU.
	 *
	 * @param {number} start - Position at which to start update.
	 * @param {number} count - The number of components to update.
	 */
	addUpdateRange( start, count ) {

		this.updateRanges.push( { start, count } );

	}

	/**
	 * Clears the update ranges.
	 */
	clearUpdateRanges() {

		this.updateRanges.length = 0;

	}

	/**
	 * The data type of the buffer elements.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The element type.
	 */
	getElementType( builder ) {

		return this.getNodeType( builder );

	}

	/**
	 * Overwrites the default implementation to return a fixed value `'buffer'`.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( /*builder*/ ) {

		return 'buffer';

	}

}

/**
 * TSL function for creating a buffer node.
 *
 * @tsl
 * @function
 * @param {Array<number>} value - Array-like buffer data.
 * @param {string} type - The data type of a buffer element.
 * @param {number} count - The count of buffer elements.
 * @returns {BufferNode}
 */
const buffer = ( value, type, count ) => new BufferNode( value, type, count );

/**
 * Represents the element access on uniform array nodes.
 *
 * @augments ArrayElementNode
 */
class UniformArrayElementNode extends ArrayElementNode {

	static get type() {

		return 'UniformArrayElementNode';

	}

	/**
	 * Constructs a new buffer node.
	 *
	 * @param {UniformArrayNode} uniformArrayNode - The uniform array node to access.
	 * @param {IndexNode} indexNode - The index data that define the position of the accessed element in the array.
	 */
	constructor( uniformArrayNode, indexNode ) {

		super( uniformArrayNode, indexNode );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isArrayBufferElementNode = true;

	}

	generate( builder ) {

		const snippet = super.generate( builder );
		const type = this.getNodeType( builder );
		const paddedType = this.node.getPaddedType( builder );

		return builder.format( snippet, paddedType, type );

	}

}

/**
 * Similar to {@link BufferNode} this module represents array-like data as
 * uniform buffers. Unlike {@link BufferNode}, it can handle more common
 * data types in the array (e.g `three.js` primitives) and automatically
 * manage buffer padding. It should be the first choice when working with
 * uniforms buffers.
 * ```js
 * const tintColors = uniformArray( [
 * 	new Color( 1, 0, 0 ),
 * 	new Color( 0, 1, 0 ),
 * 	new Color( 0, 0, 1 )
 * ], 'color' );
 *
 * const redColor = tintColors.element( 0 );
 *
 * @augments BufferNode
 */
class UniformArrayNode extends BufferNode {

	static get type() {

		return 'UniformArrayNode';

	}

	/**
	 * Constructs a new uniform array node.
	 *
	 * @param {Array<any>} value - Array holding the buffer data.
	 * @param {?string} [elementType=null] - The data type of a buffer element.
	 */
	constructor( value, elementType = null ) {

		super( null );

		/**
		 * Array holding the buffer data. Unlike {@link BufferNode}, the array can
		 * hold number primitives as well as three.js objects like vectors, matrices
		 * or colors.
		 *
		 * @type {Array<any>}
		 */
		this.array = value;

		/**
		 * The data type of an array element.
		 *
		 * @type {string}
		 */
		this.elementType = elementType === null ? getValueType( value[ 0 ] ) : elementType;

		/**
		 * Overwritten since uniform array nodes are updated per render.
		 *
		 * @type {string}
		 * @default 'render'
		 */
		this.updateType = NodeUpdateType.RENDER;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isArrayBufferNode = true;

	}

	/**
	 * This method is overwritten since the node type is inferred from the
	 * padded type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		return this.getPaddedType( builder );

	}

	/**
	 * The data type of the array elements.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The element type.
	 */
	getElementType() {

		return this.elementType;

	}

	/**
	 * Returns the padded type based on the element type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The padded type.
	 */
	getPaddedType( builder ) {

		const elementType = this.elementType;

		let paddedType = 'vec4';

		if ( elementType === 'mat2' ) {

			paddedType = builder.renderer.backend.isWebGLBackend === true ? 'vec4' : 'mat2';

		} else if ( /mat/.test( elementType ) === true ) {

			paddedType = 'mat4';

		} else if ( elementType.charAt( 0 ) === 'i' ) {

			paddedType = 'ivec4';

		} else if ( elementType.charAt( 0 ) === 'u' ) {

			paddedType = 'uvec4';

		}

		return paddedType;

	}

	update( /*frame*/ ) {

		this.updateBuffer();

	}

	/**
	 * Composes a user-defined update with the buffer transfer.
	 *
	 * @param {Function} callback - The update function.
	 * @param {string} updateType - The update type.
	 * @return {UniformArrayNode} A reference to this node.
	 */
	onUpdate( callback, updateType ) {

		callback = callback.bind( this );

		return super.onUpdate( ( frame, self ) => {

			callback( frame, self );

			this.updateBuffer();

		}, updateType );

	}

	/**
	 * The method makes sure to correctly transfer the data from the (complex) objects
	 * in the array to the internal, correctly padded value buffer.
	 */
	updateBuffer() {

		const { array, value } = this;

		const elementType = this.elementType;

		if ( elementType === 'float' || elementType === 'int' || elementType === 'uint' ) {

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

				const index = i * 4;

				value[ index ] = array[ i ];

			}

		} else if ( elementType === 'color' ) {

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

				const index = i * 4;
				const vector = array[ i ];

				value[ index ] = vector.r;
				value[ index + 1 ] = vector.g;
				value[ index + 2 ] = vector.b || 0;
				//value[ index + 3 ] = vector.a || 0;

			}

		} else if ( elementType === 'mat2' ) {

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

				const index = i * 4;
				const matrix = array[ i ];

				value[ index ] = matrix.elements[ 0 ];
				value[ index + 1 ] = matrix.elements[ 1 ];
				value[ index + 2 ] = matrix.elements[ 2 ];
				value[ index + 3 ] = matrix.elements[ 3 ];

			}

		} else if ( elementType === 'mat3' ) {

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

				const index = i * 16;
				const matrix = array[ i ];

				value[ index ] = matrix.elements[ 0 ];
				value[ index + 1 ] = matrix.elements[ 1 ];
				value[ index + 2 ] = matrix.elements[ 2 ];

				value[ index + 4 ] = matrix.elements[ 3 ];
				value[ index + 5 ] = matrix.elements[ 4 ];
				value[ index + 6 ] = matrix.elements[ 5 ];

				value[ index + 8 ] = matrix.elements[ 6 ];
				value[ index + 9 ] = matrix.elements[ 7 ];
				value[ index + 10 ] = matrix.elements[ 8 ];

				value[ index + 15 ] = 1;

			}

		} else if ( elementType === 'mat4' ) {

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

				const index = i * 16;
				const matrix = array[ i ];

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

					value[ index + i ] = matrix.elements[ i ];

				}

			}

		} else {

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

				const index = i * 4;
				const vector = array[ i ];

				value[ index ] = vector.x;
				value[ index + 1 ] = vector.y;
				value[ index + 2 ] = vector.z || 0;
				value[ index + 3 ] = vector.w || 0;

			}

		}

	}

	/**
	 * Implement the value buffer creation based on the array data.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @return {null}
	 */
	setup( builder ) {

		const length = this.array.length;
		const elementType = this.elementType;

		let arrayType = Float32Array;

		const paddedType = this.getPaddedType( builder );
		const paddedElementLength = builder.getTypeLength( paddedType );

		if ( elementType.charAt( 0 ) === 'i' ) arrayType = Int32Array;
		if ( elementType.charAt( 0 ) === 'u' ) arrayType = Uint32Array;

		this.value = new arrayType( length * paddedElementLength );
		this.bufferCount = length;
		this.bufferType = paddedType;

		this.updateBuffer(); // initialize the buffer values

		return super.setup( builder );

	}

	/**
	 * Overwrites the default `element()` method to provide element access
	 * based on {@link UniformArrayNode}.
	 *
	 * @param {IndexNode} indexNode - The index node.
	 * @return {UniformArrayElementNode}
	 */
	element( indexNode ) {

		return new UniformArrayElementNode( this, nodeObject( indexNode ) );

	}

}

/**
 * TSL function for creating an uniform array node.
 *
 * @tsl
 * @function
 * @param {Array<any>} values - Array-like data.
 * @param {?string} [nodeType] - The data type of the array elements.
 * @returns {UniformArrayNode}
 */
const uniformArray = ( values, nodeType ) => new UniformArrayNode( values, nodeType );

let _screenSizeVec, _viewportVec;

/**
 * This node provides a collection of screen related metrics.
 * Depending on {@link ScreenNode#scope}, the nodes can represent
 * resolution or viewport data as well as fragment or uv coordinates.
 *
 * @augments Node
 */
class ScreenNode extends Node {

	static get type() {

		return 'ScreenNode';

	}

	/**
	 * Constructs a new screen node.
	 *
	 * @param {('coordinate'|'viewport'|'size'|'uv')} scope - The node's scope.
	 */
	constructor( scope ) {

		super();

		/**
		 * The node represents different metric depending on which scope is selected.
		 *
		 * - `ScreenNode.COORDINATE`: Window-relative coordinates of the current fragment according to WebGPU standards.
		 * - `ScreenNode.VIEWPORT`: The current viewport defined as a four-dimensional vector.
		 * - `ScreenNode.SIZE`: The dimensions of the current bound framebuffer.
		 * - `ScreenNode.UV`: Normalized coordinates.
		 *
		 * @type {('coordinate'|'viewport'|'size'|'uv')}
		 */
		this.scope = scope;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isViewportNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * This method is overwritten since the node type depends on the selected scope.
	 *
	 * @return {('vec2'|'vec4')} The node type.
	 */
	generateNodeType() {

		if ( this.scope === ScreenNode.VIEWPORT ) return 'vec4';
		else return 'vec2';

	}

	/**
	 * `ScreenNode` implements {@link Node#update} to retrieve viewport and size information
	 * from the current renderer.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	update( { renderer } ) {

		const renderTarget = renderer.getRenderTarget();

		if ( this.scope === ScreenNode.VIEWPORT ) {

			if ( renderTarget !== null ) {

				_viewportVec.copy( renderTarget.viewport );

			} else {

				renderer.getViewport( _viewportVec );

				_viewportVec.multiplyScalar( renderer.getPixelRatio() );

			}

		} else {

			if ( renderTarget !== null ) {

				_screenSizeVec.width = renderTarget.width;
				_screenSizeVec.height = renderTarget.height;

			} else {

				renderer.getDrawingBufferSize( _screenSizeVec );

			}

		}

	}

	setup( /*builder*/ ) {

		const scope = this.scope;

		let output = null;

		if ( scope === ScreenNode.SIZE ) {

			output = uniform( _screenSizeVec || ( _screenSizeVec = new Vector2() ) ).setGroup( renderGroup );

		} else if ( scope === ScreenNode.VIEWPORT ) {

			output = uniform( _viewportVec || ( _viewportVec = new Vector4() ) ).setGroup( renderGroup );

		} else {

			output = vec2( screenCoordinate.div( screenSize ) );

		}

		//

		let updateType = NodeUpdateType.NONE;

		if ( this.scope === ScreenNode.SIZE || this.scope === ScreenNode.VIEWPORT ) {

			updateType = NodeUpdateType.RENDER;

		}

		this.updateType = updateType;

		//

		return output;

	}

	generate( builder ) {

		if ( this.scope === ScreenNode.COORDINATE ) {

			let coord = builder.getFragCoord();

			if ( builder.isFlipY() ) {

				// follow webgpu standards

				const size = builder.getNodeProperties( screenSize ).outputNode.build( builder );

				coord = `${ builder.getType( 'vec2' ) }( ${ coord }.x, ${ size }.y - ${ coord }.y )`;

			}

			return coord;

		}

		return super.generate( builder );

	}

}

ScreenNode.COORDINATE = 'coordinate';
ScreenNode.VIEWPORT = 'viewport';
ScreenNode.SIZE = 'size';
ScreenNode.UV = 'uv';

// Screen

/**
 * TSL object that represents the current DPR.
 *
 * @tsl
 * @type {UniformNode<float>}
 */
const screenDPR = /*@__PURE__*/ uniform( 1 ).setGroup( renderGroup ).onRenderUpdate( ( { renderer } ) => renderer.getPixelRatio() );

/**
 * TSL object that represents normalized screen coordinates, unitless in `[0, 1]`.
 *
 * @tsl
 * @type {ScreenNode<vec2>}
 */
const screenUV = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.UV );

/**
 * TSL object that represents the screen resolution in physical pixel units.
 *
 * @tsl
 * @type {ScreenNode<vec2>}
 */
const screenSize = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.SIZE );

/**
 * TSL object that represents the current `x`/`y` pixel position on the screen in physical pixel units.
 *
 * @tsl
 * @type {ScreenNode<vec2>}
 */
const screenCoordinate = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.COORDINATE );

// Viewport

/**
 * TSL object that represents the viewport rectangle as `x`, `y`, `width` and `height` in physical pixel units.
 *
 * @tsl
 * @type {ScreenNode<vec4>}
 */
const viewport = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.VIEWPORT );

/**
 * TSL object that represents the viewport resolution in physical pixel units.
 *
 * @tsl
 * @type {ScreenNode<vec2>}
 */
const viewportSize = viewport.zw;

/**
 * TSL object that represents the current `x`/`y` pixel position on the viewport in physical pixel units.
 *
 * @tsl
 * @type {ScreenNode<vec2>}
 */
const viewportCoordinate = /*@__PURE__*/ screenCoordinate.sub( viewport.xy );

/**
 * TSL object that represents normalized viewport coordinates, unitless in `[0, 1]`.
 *
 * @tsl
 * @type {ScreenNode<vec2>}
 */
const viewportUV = /*@__PURE__*/ viewportCoordinate.div( viewportSize );

// Cache single-camera uniforms globally. Array uniforms are cached per renderer and
// camera count so cameras with different array sizes cannot overwrite each other's
// buffers, while builders with matching counts still share them.

const _cameraArrays = /*@__PURE__*/ new WeakMap();

function getCameraArray( renderer, camera, name, create ) {

	let arrays = _cameraArrays.get( renderer );

	if ( arrays === undefined ) {

		arrays = new Map();
		_cameraArrays.set( renderer, arrays );

	}

	const key = name + ':' + camera.cameras.length;

	let array = arrays.get( key );

	if ( array === undefined ) {

		array = create( camera );
		arrays.set( key, array );

	}

	return array;

}

let _cameraProjectionMatrixBase = null;

let _cameraProjectionMatrixInverseBase = null;

let _cameraViewMatrixBase = null;

let _cameraWorldMatrixBase = null;

let _cameraNormalMatrixBase = null;

let _cameraPositionBase = null;

let _cameraViewportBase = null;

/**
 * TSL object that represents the current `index` value of the camera if used ArrayCamera.
 *
 * @tsl
 * @type {UniformNode<uint>}
 */
const cameraIndex = /*@__PURE__*/ uniform( 0, 'uint' ).setName( 'u_cameraIndex' ).setGroup( sharedUniformGroup( 'cameraIndex' ) ).toVarying( 'v_cameraIndex' );

/**
 * TSL object that represents the `near` value of the camera used for the current render.
 *
 * @tsl
 * @type {UniformNode<float>}
 */
const cameraNear = /*@__PURE__*/ uniform( 'float' ).setName( 'cameraNear' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.near );

/**
 * TSL object that represents the `far` value of the camera used for the current render.
 *
 * @tsl
 * @type {UniformNode<float>}
 */
const cameraFar = /*@__PURE__*/ uniform( 'float' ).setName( 'cameraFar' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.far );

/**
 * TSL object that represents the projection matrix of the camera used for the current render.
 *
 * @tsl
 * @type {UniformNode<mat4>}
 */
const cameraProjectionMatrix = /*@__PURE__*/ ( Fn( ( { camera, renderer } ) => {

	let cameraProjectionMatrix;

	if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

		const cameraArray = getCameraArray( renderer, camera, 'cameraProjectionMatrices', ( camera ) => {

			const values = camera.cameras.map( ( subCamera ) => subCamera.projectionMatrix );

			return uniformArray( values ).setGroup( renderGroup ).setName( 'cameraProjectionMatrices' ).onRenderUpdate( ( { camera }, self ) => {

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

					self.array[ i ] = camera.cameras[ i ].projectionMatrix;

				}

			} );

		} );

		cameraProjectionMatrix = cameraArray.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex );

	} else {

		if ( _cameraProjectionMatrixBase === null ) {

			_cameraProjectionMatrixBase = uniform( camera.projectionMatrix ).setName( 'cameraProjectionMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrix );

		}

		cameraProjectionMatrix = _cameraProjectionMatrixBase;

	}

	return cameraProjectionMatrix;

} ).once() )();

/**
 * TSL object that represents the inverse projection matrix of the camera used for the current render.
 *
 * @tsl
 * @type {UniformNode<mat4>}
 */
const cameraProjectionMatrixInverse = /*@__PURE__*/ ( Fn( ( { camera, renderer } ) => {

	let cameraProjectionMatrixInverse;

	if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

		const cameraArray = getCameraArray( renderer, camera, 'cameraProjectionMatricesInverse', ( camera ) => {

			const values = camera.cameras.map( ( subCamera ) => subCamera.projectionMatrixInverse );

			return uniformArray( values ).setGroup( renderGroup ).setName( 'cameraProjectionMatricesInverse' ).onRenderUpdate( ( { camera }, self ) => {

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

					self.array[ i ] = camera.cameras[ i ].projectionMatrixInverse;

				}

			} );

		} );

		cameraProjectionMatrixInverse = cameraArray.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex );

	} else {

		if ( _cameraProjectionMatrixInverseBase === null ) {

			_cameraProjectionMatrixInverseBase = uniform( camera.projectionMatrixInverse ).setName( 'cameraProjectionMatrixInverse' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrixInverse );

		}

		cameraProjectionMatrixInverse = _cameraProjectionMatrixInverseBase;

	}

	return cameraProjectionMatrixInverse;

} ).once() )();

/**
 * TSL object that represents the view matrix of the camera used for the current render.
 *
 * @tsl
 * @type {UniformNode<mat4>}
 */
const cameraViewMatrix = /*@__PURE__*/ ( Fn( ( { camera, renderer } ) => {

	let cameraViewMatrix;

	if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

		const cameraArray = getCameraArray( renderer, camera, 'cameraViewMatrices', ( camera ) => {

			const values = camera.cameras.map( ( subCamera ) => subCamera.matrixWorldInverse );

			return uniformArray( values ).setGroup( renderGroup ).setName( 'cameraViewMatrices' ).onRenderUpdate( ( { camera }, self ) => {

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

					self.array[ i ] = camera.cameras[ i ].matrixWorldInverse;

				}

			} );

		} );

		cameraViewMatrix = cameraArray.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex );

	} else {

		if ( _cameraViewMatrixBase === null ) {

			_cameraViewMatrixBase = uniform( camera.matrixWorldInverse ).setName( 'cameraViewMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorldInverse );

		}

		cameraViewMatrix = _cameraViewMatrixBase;

	}

	return cameraViewMatrix;

} ).once() )();

/**
 * TSL object that represents the world matrix of the camera used for the current render.
 *
 * @tsl
 * @type {UniformNode<mat4>}
 */
const cameraWorldMatrix = /*@__PURE__*/ ( Fn( ( { camera, renderer } ) => {

	let cameraWorldMatrix;

	if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

		const cameraArray = getCameraArray( renderer, camera, 'cameraWorldMatrices', ( camera ) => {

			const values = camera.cameras.map( ( subCamera ) => subCamera.matrixWorld );

			return uniformArray( values ).setGroup( renderGroup ).setName( 'cameraWorldMatrices' ).onRenderUpdate( ( { camera }, self ) => {

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

					self.array[ i ] = camera.cameras[ i ].matrixWorld;

				}

			} );

		} );

		cameraWorldMatrix = cameraArray.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex );

	} else {

		if ( _cameraWorldMatrixBase === null ) {

			_cameraWorldMatrixBase = uniform( camera.matrixWorld ).setName( 'cameraWorldMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorld );

		}

		cameraWorldMatrix = _cameraWorldMatrixBase;

	}

	return cameraWorldMatrix;

} ).once() )();

/**
 * TSL object that represents the normal matrix of the camera used for the current render.
 *
 * @tsl
 * @type {UniformNode<mat3>}
 */
const cameraNormalMatrix = /*@__PURE__*/ ( Fn( ( { camera, renderer } ) => {

	let cameraNormalMatrix;

	if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

		const cameraArray = getCameraArray( renderer, camera, 'cameraNormalMatrices', ( camera ) => {

			const values = camera.cameras.map( ( subCamera ) => subCamera.normalMatrix );

			return uniformArray( values ).setGroup( renderGroup ).setName( 'cameraNormalMatrices' ).onRenderUpdate( ( { camera }, self ) => {

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

					self.array[ i ] = camera.cameras[ i ].normalMatrix;

				}

			} );

		} );

		cameraNormalMatrix = cameraArray.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex );

	} else {

		if ( _cameraNormalMatrixBase === null ) {

			_cameraNormalMatrixBase = uniform( camera.normalMatrix ).setName( 'cameraNormalMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.normalMatrix );

		}

		cameraNormalMatrix = _cameraNormalMatrixBase;

	}

	return cameraNormalMatrix;

} ).once() )();

/**
 * TSL object that represents the position in world space of the camera used for the current render.
 *
 * @tsl
 * @type {UniformNode<vec3>}
 */
const cameraPosition = /*@__PURE__*/ ( Fn( ( { camera, renderer } ) => {

	let cameraPosition;

	if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

		const cameraArray = getCameraArray( renderer, camera, 'cameraPositions', ( camera ) => {

			const positions = [];

			for ( let i = 0, l = camera.cameras.length; i < l; i ++ ) {

				positions.push( new Vector3() );

			}

			return uniformArray( positions ).setGroup( renderGroup ).setName( 'cameraPositions' ).onRenderUpdate( ( { camera }, self ) => {

				const subCameras = camera.cameras;
				const array = self.array;

				for ( let i = 0, l = subCameras.length; i < l; i ++ ) {

					array[ i ].setFromMatrixPosition( subCameras[ i ].matrixWorld );

				}

			} );

		} );

		cameraPosition = cameraArray.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex );

	} else {

		if ( _cameraPositionBase === null ) {

			_cameraPositionBase = uniform( new Vector3() ).setName( 'cameraPosition' ).setGroup( renderGroup ).onRenderUpdate( ( { camera }, self ) => self.value.setFromMatrixPosition( camera.matrixWorld ) );

		}

		cameraPosition = _cameraPositionBase;

	}

	return cameraPosition;

} ).once() )();


/**
 * TSL object that represents the viewport of the camera used for the current render.
 *
 * @tsl
 * @type {UniformNode<vec4>}
 */
const cameraViewport = /*@__PURE__*/ ( Fn( ( { camera, renderer } ) => {

	let cameraViewport;

	if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

		const cameraArray = getCameraArray( renderer, camera, 'cameraViewports', ( camera ) => {

			const values = camera.cameras.map( ( subCamera ) => subCamera.viewport );

			return uniformArray( values, 'vec4' ).setGroup( renderGroup ).setName( 'cameraViewports' ).onRenderUpdate( ( { camera }, self ) => {

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

					self.array[ i ] = camera.cameras[ i ].viewport;

				}

			} );

		} );

		cameraViewport = cameraArray.element( cameraIndex );

	} else {

		if ( _cameraViewportBase === null ) {

			// Fallback for single camera
			_cameraViewportBase = vec4( 0, 0, screenSize.x, screenSize.y ).toConst( 'cameraViewport' );

		}

		cameraViewport = _cameraViewportBase;

	}

	return cameraViewport;

} ).once() )();

const _modelViewMatrix = /*@__PURE__*/ new Matrix4();

/**
 * This type of node is a specialized version of `Object3DNode`
 * with larger set of model related metrics. Unlike `Object3DNode`,
 * `ModelNode` extracts the reference to the 3D object from the
 * current node frame state.
 *
 * @augments Object3DNode
 */
class ModelNode extends Object3DNode {

	static get type() {

		return 'ModelNode';

	}

	/**
	 * Constructs a new object model node.
	 *
	 * @param {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} scope - The node represents a different type of transformation depending on the scope.
	 */
	constructor( scope ) {

		super( scope );

	}

	/**
	 * Extracts the model reference from the frame state and then
	 * updates the uniform value depending on the scope.
	 *
	 * @param {NodeFrame} frame - The current node frame.
	 */
	update( frame ) {

		this.object3d = frame.object;

		super.update( frame );

	}

}

/**
 * TSL object that represents the object's direction in world space.
 *
 * @tsl
 * @type {ModelNode<vec3>}
 */
const modelDirection = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.DIRECTION );

/**
 * TSL object that represents the object's world matrix.
 *
 * @tsl
 * @type {ModelNode<mat4>}
 */
const modelWorldMatrix = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX );

/**
 * TSL object that represents the object's position in world space.
 *
 * @tsl
 * @type {ModelNode<vec3>}
 */
const modelPosition = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.POSITION );

/**
 * TSL object that represents the object's scale in world space.
 *
 * @tsl
 * @type {ModelNode<vec3>}
 */
const modelScale = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.SCALE );

/**
 * TSL object that represents the object's position in view/camera space.
 *
 * @tsl
 * @type {ModelNode<vec3>}
 */
const modelViewPosition = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.VIEW_POSITION );

/**
 * TSL object that represents the object's radius.
 *
 * @tsl
 * @type {ModelNode<float>}
 */
const modelRadius = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.RADIUS );

/**
 * TSL object that represents the object's normal matrix.
 *
 * @tsl
 * @type {UniformNode<mat3>}
 */
const modelNormalMatrix = /*@__PURE__*/ uniform( new Matrix3() ).onObjectUpdate( ( { object }, self ) => self.value.getNormalMatrix( object.matrixWorld ) );

/**
 * TSL object that represents the object's inverse world matrix.
 *
 * @tsl
 * @type {UniformNode<mat4>}
 */
const modelWorldMatrixInverse = /*@__PURE__*/ uniform( new Matrix4() ).onObjectUpdate( ( { object }, self ) => self.value.copy( object.matrixWorld ).invert() );

/**
 * TSL object that represents the object's model view matrix.
 *
 * @tsl
 * @type {Node<mat4>}
 */
const modelViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => {

	return builder.context.modelViewMatrix || mediumpModelViewMatrix;

} ).once() )().toVar( 'modelViewMatrix' );

// GPU Precision

/**
 * TSL object that represents the object's model view in `mediump` precision.
 *
 * @tsl
 * @type {Node<mat4>}
 */
const mediumpModelViewMatrix = /*@__PURE__*/ cameraViewMatrix.mul( modelWorldMatrix );

// CPU Precision

/**
 * TSL object that represents the object's model view in `highp` precision
 * which is achieved by computing the matrix in JS and not in the shader.
 *
 * @tsl
 * @type {Node<mat4>}
 */
const highpModelViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => {

	builder.context.isHighPrecisionModelViewMatrix = true;

	const camera = builder.camera;

	let highpModelViewMatrix;

	if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

		const matrices = [];

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

			matrices.push( new Matrix4() );

		}

		const modelViewMatrices = uniformArray( matrices ).onObjectUpdate( ( { object, camera }, self ) => {

			const subCameras = camera.cameras;
			const array = self.array;

			for ( let i = 0, l = subCameras.length; i < l; i ++ ) {

				array[ i ].multiplyMatrices( subCameras[ i ].matrixWorldInverse, object.matrixWorld );

			}

		} );

		highpModelViewMatrix = modelViewMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex );

	} else {

		highpModelViewMatrix = uniform( 'mat4' ).onObjectUpdate( ( { object, camera } ) => {

			return object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );

		} );

	}

	return highpModelViewMatrix;

} ).once() )().toVar( 'highpModelViewMatrix' );

/**
 * TSL object that represents the object's model normal view in `highp` precision
 * which is achieved by computing the matrix in JS and not in the shader.
 *
 * @tsl
 * @type {Node<mat3>}
 */
const highpModelNormalViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => {

	const isHighPrecisionModelViewMatrix = builder.context.isHighPrecisionModelViewMatrix;

	const camera = builder.camera;

	let highpModelNormalViewMatrix;

	if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

		const matrices = [];

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

			matrices.push( new Matrix3() );

		}

		const normalViewMatrices = uniformArray( matrices ).onObjectUpdate( ( { object, camera }, self ) => {

			const subCameras = camera.cameras;
			const array = self.array;

			for ( let i = 0, l = subCameras.length; i < l; i ++ ) {

				_modelViewMatrix.multiplyMatrices( subCameras[ i ].matrixWorldInverse, object.matrixWorld );

				array[ i ].getNormalMatrix( _modelViewMatrix );

			}

		} );

		highpModelNormalViewMatrix = normalViewMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex );

	} else {

		highpModelNormalViewMatrix = uniform( 'mat3' ).onObjectUpdate( ( { object, camera } ) => {

			if ( isHighPrecisionModelViewMatrix !== true ) {

				object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );

			}

			return object.normalMatrix.getNormalMatrix( object.modelViewMatrix );

		} );

	}

	return highpModelNormalViewMatrix;

} ).once() )().toVar( 'highpModelNormalViewMatrix' );

/**
 * TSL object that represents the clip space position of the current rendered object.
 *
 * @tsl
 * @type {VaryingNode<vec4>}
 */
const clipSpace = /*@__PURE__*/ ( Fn( ( builder ) => {

	if ( builder.shaderStage !== 'fragment' ) {

		warnOnce( 'TSL: `clipSpace` is only available in fragment stage.' );

		return vec4();

	}

	return builder.context.clipSpace.toVarying( 'v_clipSpace' );

} ).once() )();

/**
 * TSL object that represents the position attribute of the current rendered object.
 *
 * @tsl
 * @type {AttributeNode<vec3>}
 */
const positionGeometry = /*@__PURE__*/ attribute( 'position', 'vec3' );

/**
 * TSL object that represents the transformed vertex position in local space of the current rendered object.
 *
 * The term "transformed" indicates that an object or material's properties, such as skinning, batch,
 * instancing, or displacement mapping, will change the vertex position of the node when present.
 * To use the pre-transformed local space position of the object, use {@link positionGeometry}.
 *
 * @tsl
 * @type {AttributeNode<vec3>}
 */
const positionLocal = /*@__PURE__*/ positionGeometry.toVarying( 'positionLocal' );

/**
 * TSL object that represents the previous vertex position in local space of the current rendered object.
 * Used in context of {@link VelocityNode} for rendering motion vectors.
 *
 * @tsl
 * @type {AttributeNode<vec3>}
 */
const positionPrevious = /*@__PURE__*/ positionGeometry.toVarying( 'positionPrevious' );

/**
 * TSL object that represents the vertex position in world space of the current rendered object.
 *
 * @tsl
 * @type {VaryingNode<vec3>}
 */
const positionWorld = /*@__PURE__*/ ( Fn( ( builder ) => {

	return modelWorldMatrix.mul( positionLocal ).xyz.toVarying( builder.getSubBuildProperty( 'v_positionWorld' ) );

}, 'vec3' ).once( [ 'POSITION' ] ) )();

/**
 * TSL object that represents the position world direction of the current rendered object.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const positionWorldDirection = /*@__PURE__*/ ( Fn( () => {

	const vertexPWD = positionLocal.transformDirection( modelWorldMatrix ).toVarying( 'v_positionWorldDirection' );

	return vertexPWD.normalize().toVar( 'positionWorldDirection' );

}, 'vec3' ).once( [ 'POSITION' ] ) )();

/**
 * TSL object that represents the vertex position in view space of the current rendered object.
 *
 * @tsl
 * @type {VaryingNode<vec3>}
 */
const positionView = /*@__PURE__*/ ( Fn( ( builder ) => {

	if ( builder.shaderStage === 'fragment' && builder.material.vertexNode ) {

		// reconstruct view position from clip space

		const viewPos = cameraProjectionMatrixInverse.mul( clipSpace );

		return viewPos.xyz.div( viewPos.w ).toVar( 'positionView' );

	}

	return builder.context.setupPositionView().toVarying( 'v_positionView' );

}, 'vec3' ).once( [ 'POSITION', 'VERTEX' ] ) )();

/**
 * TSL object that represents the position view direction of the current rendered object.
 *
 * @tsl
 * @type {VaryingNode<vec3>}
 */
const positionViewDirection = /*@__PURE__*/ ( Fn( ( builder ) => {

	let output;

	if ( builder.camera.isOrthographicCamera ) {

		output = vec3( 0, 0, 1 );

	} else {

		output = positionView.negate().toVarying( 'v_positionViewDirection' ).normalize();

	}

	return output.toVar( 'positionViewDirection' );

}, 'vec3' ).once( [ 'POSITION' ] ) )();

/**
 * TSL function for creating an uv attribute node with the given index.
 *
 * @tsl
 * @function
 * @param {number} [index=0] - The uv index.
 * @return {AttributeNode<vec2>} The uv attribute node.
 */
const uv$1 = ( index = 0 ) => attribute( 'uv' + ( index > 0 ? index : '' ), 'vec2' );

/**
 * TSL function for creating an equirect uv node.
 *
 * Can be used to compute texture coordinates for projecting an
 * equirectangular texture onto a mesh for using it as the scene's
 * background.
 *
 * ```js
 * scene.backgroundNode = texture( equirectTexture, equirectUV() );
 * ```
 *
 * @tsl
 * @function
 * @param {?Node<vec3>} [direction=positionWorldDirection] - A direction vector for sampling which is by default `positionWorldDirection`.
 * @returns {Node<vec2>}
 */
const equirectUV = /*@__PURE__*/ Fn( ( [ direction = positionWorldDirection ] ) => {

	const u = direction.z.atan( direction.x ).mul( 1 / ( Math.PI * 2 ) ).add( 0.5 );
	const v = direction.y.clamp( -1, 1.0 ).asin().mul( 1 / Math.PI ).add( 0.5 );

	return vec2( u, v );

} );

/**
 * TSL function for creating an equirect direction node.
 *
 * Can be used to compute a direction vector from the given equirectangular
 * UV coordinates.
 *
 * @tsl
 * @function
 * @param {?Node<vec2>} [uv=UV()] - The equirectangular UV coordinates.
 * @returns {Node<vec3>} The computed direction vector.
 */
const equirectDirection = /*@__PURE__*/ Fn( ( [ uv = uv$1() ] ) => {

	const theta = uv.x.sub( 0.5 ).mul( Math.PI * 2 );
	const phi = uv.y.sub( 0.5 ).mul( Math.PI );
	const cosPhi = phi.cos();
	const x = cosPhi.mul( theta.cos() );
	const y = phi.sin();
	const z = cosPhi.mul( theta.sin() );

	return vec3( x, y, z );

} );

/**
 * This class allows to define multiple overloaded versions
 * of the same function. Depending on the parameters of the function
 * call, the node picks the best-fit overloaded version.
 *
 * @augments Node
 */
class FunctionOverloadingNode extends Node {

	static get type() {

		return 'FunctionOverloadingNode';

	}

	/**
	 * Constructs a new function overloading node.
	 *
	 * @param {Array<Function>} functionNodes - Array of `Fn` function definitions.
	 * @param {...Node} parametersNodes - A list of parameter nodes.
	 */
	constructor( functionNodes = [], ...parametersNodes ) {

		super();

		/**
		 * Array of `Fn` function definitions.
		 *
		 * @type {Array<Function>}
		 */
		this.functionNodes = functionNodes;

		/**
		 * A list of parameter nodes.
		 *
		 * @type {Array<Node>}
		 */
		this.parametersNodes = parametersNodes;

		/**
		 * The selected overloaded function call.
		 *
		 * @private
		 * @type {ShaderCallNodeInternal}
		 */
		this._candidateFn = null;

		/**
		 * This node is marked as global.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * This method is overwritten since the node type is inferred from
	 * the function's return type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		const candidateFn = this.getCandidateFn( builder );

		return candidateFn.shaderNode.layout.type;

	}

	/**
	 * Returns the candidate function for the current parameters.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {FunctionNode} The candidate function.
	 */
	getCandidateFn( builder ) {

		const params = this.parametersNodes;

		let candidateFn = this._candidateFn;

		if ( candidateFn === null ) {

			let bestCandidateFn = null;
			let bestScore = -1;

			for ( const functionNode of this.functionNodes ) {

				const shaderNode = functionNode.shaderNode;
				const layout = shaderNode.layout;

				if ( layout === null ) {

					throw new Error( 'THREE.FunctionOverloadingNode: FunctionNode must be a layout.' );

				}

				const inputs = layout.inputs;

				if ( params.length === inputs.length ) {

					let currentScore = 0;

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

						const param = params[ i ];
						const input = inputs[ i ];

						if ( builder.getVectorType( param.getNodeType( builder ) ) === input.type ) {

							currentScore ++;

						}

					}

					if ( currentScore > bestScore ) {

						bestCandidateFn = functionNode;
						bestScore = currentScore;

					}

				}

			}

			this._candidateFn = candidateFn = bestCandidateFn;

		}

		return candidateFn;

	}

	/**
	 * Sets up the node for the current parameters.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node} The setup node.
	 */
	setup( builder ) {

		const candidateFn = this.getCandidateFn( builder );

		return candidateFn( ...this.parametersNodes );

	}

}

const overloadingBaseFn = /*@__PURE__*/ nodeProxy( FunctionOverloadingNode );

/**
 * TSL function for creating a function overloading node.
 *
 * @tsl
 * @function
 * @param {Array<Function>} functionNodes - Array of `Fn` function definitions.
 * @returns {FunctionOverloadingNode}
 */
const overloadingFn = ( functionNodes ) => ( ...params ) => overloadingBaseFn( functionNodes, ...params );

/**
 * This node can be used to evaluate whether a primitive is front or back facing.
 *
 * @augments Node
 */
class FrontFacingNode extends Node {

	static get type() {

		return 'FrontFacingNode';

	}

	/**
	 * Constructs a new front facing node.
	 */
	constructor() {

		super( 'bool' );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isFrontFacingNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generate( builder ) {

		if ( builder.shaderStage !== 'fragment' ) return 'true';

		//

		const { material } = builder;

		if ( material.side === BackSide ) {

			return 'false';

		}

		return builder.getFrontFacing();

	}

}

/**
 * TSL object that represents whether a primitive is front or back facing
 *
 * @tsl
 * @type {FrontFacingNode<bool>}
 */
const frontFacing = /*@__PURE__*/ nodeImmutable( FrontFacingNode );

/**
 * TSL object that represents the front facing status as a number instead of a bool.
 * `1` means front facing, `-1` means back facing.
 *
 * @tsl
 * @type {Node<float>}
 */
const faceDirection = /*@__PURE__*/ float( frontFacing ).mul( 2.0 ).sub( 1.0 );

/**
 * Negates a vector if the rendering occurs on the back side of a face,
 * based on the material's side configuration.
 *
 * - If the material's side is `BackSide`, the vector is inverted (negated).
 * - If the material's side is `DoubleSide`, the vector is multiplied by `faceDirection`
 *   (negated only for back-facing fragments).
 * - If the material's side is `FrontSide` (default), the vector remains unchanged.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} vector - The vector to process.
 * @returns {Node<vec3>} The processed vector.
 */
const negateOnBackSide = /*@__PURE__*/ Fn( ( [ vector ], { material } ) => {

	const side = material.side;

	if ( side === BackSide ) {

		vector = vector.mul( -1 );

	} else if ( side === DoubleSide ) {

		vector = vector.mul( faceDirection );

	}

	return vector;

} );

/**
 * Negates a vector if the rendering occurs on the back side of a face,
 * based on the material's side configuration.
 *
 * - If the material's side is `BackSide`, the vector is inverted (negated).
 * - If the material's side is `DoubleSide`, the vector is multiplied by `faceDirection`
 *   (negated only for back-facing fragments).
 * - If the material's side is `FrontSide` (default), the vector remains unchanged.
 *
 * @tsl
 * @function
 * @deprecated since r185. Use {@link negateOnBackSide} instead.
 * @param {Node<vec3>} vector - The vector to convert.
 * @returns {Node<vec3>} The converted vector.
 */
const directionToFaceDirection = ( vector ) => {

	warnOnce( 'TSL: "directionToFaceDirection()" has been renamed to "negateOnBackSide()".' ); // @deprecated r185

	return negateOnBackSide( vector );

};

/**
 * TSL object that represents the normal attribute of the current rendered object in local space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const normalGeometry = /*@__PURE__*/ attribute( 'normal', 'vec3' );

/**
 * TSL object that represents the vertex normal of the current rendered object in local space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const normalLocal = /*@__PURE__*/ ( Fn( ( builder ) => {

	if ( builder.geometry.hasAttribute( 'normal' ) === false ) {

		warn( 'TSL: Vertex attribute "normal" not found on geometry.' );

		return vec3( 0, 1, 0 );

	}

	return normalGeometry;

}, 'vec3' ).once() )().toVar( 'normalLocal' );

/**
 * TSL object that represents the flat vertex normal of the current rendered object in view space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const normalFlat = /*@__PURE__*/ positionView.dFdx().cross( positionView.dFdy() ).normalize().toVar( 'normalFlat' );

/**
 * TSL object that represents the vertex normal of the current rendered object in view space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const normalViewGeometry = /*@__PURE__*/ ( Fn( ( builder ) => {

	let node;

	if ( builder.isFlatShading() ) {

		node = normalFlat;

	} else {

		node = transformNormalToView( normalLocal ).toVarying( 'v_normalViewGeometry' ).normalize();

	}

	return node;

}, 'vec3' ).once() )().toVar( 'normalViewGeometry' );

/**
 * TSL object that represents the vertex normal of the current rendered object in world space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const normalWorldGeometry = /*@__PURE__*/ ( Fn( ( builder ) => {

	let normal = normalViewGeometry.transformNormalByInverseViewMatrix( cameraViewMatrix );

	if ( builder.isFlatShading() !== true ) {

		normal = normal.toVarying( 'v_normalWorldGeometry' );

	}

	return normal.normalize().toVar( 'normalWorldGeometry' );

}, 'vec3' ).once() )();

/**
 * TSL object that represents the vertex normal of the current rendered object in view space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const normalView = /*@__PURE__*/ ( Fn( ( builder ) => {

	let node;

	if ( builder.subBuildFn === 'NORMAL' || builder.subBuildFn === 'VERTEX' ) {

		node = normalViewGeometry;

		if ( builder.isFlatShading() !== true ) {

			node = negateOnBackSide( node );

		}

	} else {

		// Use custom context to avoid side effects from nodes overwriting getUV, getTextureLevel in the context (e.g. EnvironmentNode)

		node = builder.context.setupNormal().context( { getUV: null, getTextureLevel: null } );

	}

	return node;

}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'normalView' );

/**
 * TSL object that represents the vertex normal of the current rendered object in world space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const normalWorld = /*@__PURE__*/ normalView.transformNormalByInverseViewMatrix( cameraViewMatrix ).toVar( 'normalWorld' );

/**
 * TSL object that represents the clearcoat vertex normal of the current rendered object in view space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const clearcoatNormalView = /*@__PURE__*/ ( Fn( ( { subBuildFn, context } ) => {

	let node;

	if ( subBuildFn === 'NORMAL' || subBuildFn === 'VERTEX' ) {

		node = normalView;

	} else {

		// Use custom context to avoid side effects from nodes overwriting getUV, getTextureLevel in the context (e.g. EnvironmentNode)

		node = context.setupClearcoatNormal().context( { getUV: null, getTextureLevel: null } );

	}

	return node;

}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'clearcoatNormalView' );

/**
 * Transforms the normal by the normal matrix of the given matrix and then normalizes the result.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} normal - The normal.
 * @param {Node<mat3|mat4>} [matrix=modelWorldMatrix] - The matrix.
 * @return {Node<vec3>} The transformed normal.
 */
const transformNormal = /*@__PURE__*/ Fn( ( [ normal, matrix = modelWorldMatrix ] ) => {

	const normalMatrix = mat3( matrix ).inverse().transpose();

	return normalMatrix.mul( normal ).normalize();

} );

addMethodChaining( 'transformNormal', transformNormal );

/**
 * Transforms the given normal from local to view space.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} normal - The normal.
 * @param {NodeBuilder} builder - The current node builder.
 * @return {Node<vec3>} The transformed normal.
 */
const transformNormalToView = /*@__PURE__*/ Fn( ( [ normal ], builder ) => {

	const modelNormalViewMatrix = builder.context.modelNormalViewMatrix;

	if ( modelNormalViewMatrix ) {

		return normal.transformNormalByViewMatrix( modelNormalViewMatrix );

	}

	//

	const transformedNormal = modelNormalMatrix.mul( normal );

	return transformedNormal.transformNormalByViewMatrix( cameraViewMatrix );

} );

/**
 * TSL function for creating a matcap uv node.
 *
 * Can be used to compute texture coordinates for projecting a
 * matcap onto a mesh. Used by {@link MeshMatcapNodeMaterial}.
 *
 * @tsl
 * @function
 * @returns {Node<vec2>} The matcap UV coordinates.
 */
const matcapUV = /*@__PURE__*/ Fn( () => {

	const x = vec3( positionViewDirection.z, 0, positionViewDirection.x.negate() ).normalize();
	const y = positionViewDirection.cross( x );

	return vec2( x.dot( normalView ), y.dot( normalView ) ).mul( 0.495 ).add( 0.5 ); // 0.495 to remove artifacts caused by undersized matcap disks

} ).once( [ 'NORMAL', 'VERTEX' ] )().toVar( 'matcapUV' );

/**
 * A special type of uniform node that computes the
 * maximum mipmap level for a given texture node.
 *
 * ```js
 * const level = maxMipLevel( textureNode );
 * ```
 *
 * @augments UniformNode
 */
class MaxMipLevelNode extends UniformNode {

	static get type() {

		return 'MaxMipLevelNode';

	}

	/**
	 * Constructs a new max mip level node.
	 *
	 * @param {TextureNode} textureNode - The texture node to compute the max mip level for.
	 */
	constructor( textureNode ) {

		super( 0 );

		/**
		 * The texture node to compute the max mip level for.
		 *
		 * @private
		 * @type {TextureNode}
		 */
		this._textureNode = textureNode;

		/**
		 * The `updateType` is set to `NodeUpdateType.FRAME` since the node updates
		 * the texture once per frame in its {@link MaxMipLevelNode#update} method.
		 *
		 * @type {string}
		 * @default 'frame'
		 */
		this.updateType = NodeUpdateType.FRAME;

	}

	/**
	 * The texture node to compute the max mip level for.
	 *
	 * @readonly
	 * @type {TextureNode}
	 */
	get textureNode() {

		return this._textureNode;

	}

	/**
	 * The texture.
	 *
	 * @readonly
	 * @type {Texture}
	 */
	get texture() {

		return this._textureNode.value;

	}

	update() {

		const texture = this.texture;
		const images = texture.images;
		const image = ( images && images.length > 0 ) ? ( ( images[ 0 ] && images[ 0 ].image ) || images[ 0 ] ) : texture.image;

		if ( image && image.width !== undefined ) {

			const { width, height } = image;

			this.value = Math.log2( Math.max( width, height ) );

		}

	}

}

/**
 * TSL function for creating a max mip level node.
 *
 * @tsl
 * @function
 * @param {TextureNode} textureNode - The texture node to compute the max mip level for.
 * @returns {MaxMipLevelNode}
 */
const maxMipLevel = /*@__PURE__*/ nodeProxy( MaxMipLevelNode ).setParameterLength( 1 );

/**
 * Represents the elapsed time in seconds.
 *
 * @tsl
 * @type {UniformNode<float>}
 */
const time = /*@__PURE__*/ uniform( 0 ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.time );

/**
 * Represents the delta time in seconds.
 *
 * @tsl
 * @type {UniformNode<float>}
 */
const deltaTime = /*@__PURE__*/ uniform( 0 ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.deltaTime );

/**
 * Represents the current frame ID.
 *
 * @tsl
 * @type {UniformNode<uint>}
 */
const frameId = /*@__PURE__*/ uniform( 0, 'uint' ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.frameId );

/**
 * Generates a sine wave oscillation based on a timer.
 *
 * @tsl
 * @function
 * @param {Node<float>} t - The timer to generate the oscillation with.
 * @return {Node<float>} The oscillation node.
 */
const oscSine = ( t = time ) => t.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 );

/**
 * Generates a square wave oscillation based on a timer.
 *
 * @tsl
 * @function
 * @param {Node<float>} t - The timer to generate the oscillation with.
 * @return {Node<float>} The oscillation node.
 */
const oscSquare = ( t = time ) => t.fract().round();

/**
 * Generates a triangle wave oscillation based on a timer.
 *
 * @tsl
 * @function
 * @param {Node<float>} t - The timer to generate the oscillation with.
 * @return {Node<float>} The oscillation node.
 */
const oscTriangle = ( t = time ) => t.add( 0.5 ).fract().mul( 2 ).sub( 1 ).abs();

/**
 * Generates a sawtooth wave oscillation based on a timer.
 *
 * @tsl
 * @function
 * @param {Node<float>} t - The timer to generate the oscillation with.
 * @return {Node<float>} The oscillation node.
 */
const oscSawtooth = ( t = time ) => t.fract();

/**
 * Packs a normal vector into a color value.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} node - The direction to pack.
 * @return {Node<vec3>} The color.
 */
const packNormalToRGB = ( node ) => nodeObject( node ).mul( 0.5 ).add( 0.5 );

/**
 * Unpacks a color value into a normal vector.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} node - The color to unpack.
 * @return {Node<vec3>} The direction.
 */
const unpackRGBToNormal = ( node ) => nodeObject( node ).mul( 2.0 ).sub( 1 );

/**
 * Unpacks a tangent space normal, reconstructing the Z component by projecting the X,Y coordinates onto the hemisphere.
 * The X,Y coordinates are expected to be in the [-1, 1] range.
 *
 * @tsl
 * @function
 * @param {Node<vec2>} xy - The X,Y coordinates of the normal.
 * @return {Node<vec3>} The resulting normal.
 */
const unpackNormal = ( xy ) => vec3( xy, sqrt( saturate( float( 1.0 ).sub( dot( xy, xy ) ) ) ) );

/**
 * @tsl
 * @function
 * @deprecated since r185. Use {@link packNormalToRGB} instead.
 * @param {Node<vec3>} node - The direction to pack.
 * @returns {Node<vec3>}
 */
const directionToColor = ( node ) => {

	warnOnce( 'TSL: "directionToColor()" has been renamed to "packNormalToRGB()".' ); // @deprecated r185

	return packNormalToRGB( node );

};

/**
 * @tsl
 * @function
 * @deprecated since r185. Use {@link unpackRGBToNormal} instead.
 * @param {Node<vec3>} node - The color to unpack.
 * @returns {Node<vec3>}
 */
const colorToDirection = ( node ) => {

	warnOnce( 'TSL: "colorToDirection()" has been renamed to "unpackRGBToNormal()".' ); // @deprecated r185

	return unpackRGBToNormal( node );

};

/**
 * Applies a rotation to the given position node.
 *
 * @augments Node
 */
class RotateNode extends Node {

	static get type() {

		return 'RotateNode';

	}

	/**
	 * Constructs a new rotate node.
	 *
	 * @param {Node} positionNode - The position node.
	 * @param {Node} rotationNode - Represents the rotation that is applied to the position node. Depending
	 * on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value.
	 * @param {string} [order='XYZ'] - The Euler rotation order. Only used for 3D rotation.
	 */
	constructor( positionNode, rotationNode, order = 'XYZ' ) {

		super();

		/**
		 * The position node.
		 *
		 * @type {Node}
		 */
		this.positionNode = positionNode;

		/**
		 * Represents the rotation that is applied to the position node.
		 * Depending on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value.
		 *
		 * @type {Node}
		 */
		this.rotationNode = rotationNode;

		/**
		 * The Euler rotation order.
		 *
		 * @private
		 * @type {string}
		 * @default 'XYZ'
		 */
		this._order = order;

	}

	/**
	 * Overwrites the default `customCacheKey()` implementation by including the
	 * Euler order into the cache key.
	 *
	 * @return {number} The hash.
	 */
	customCacheKey() {

		return hashString( this._order );

	}

	/**
	 * Sets the Euler rotation order.
	 *
	 * @param {string} value - The Euler rotation order.
	 * @return {RotateNode} A reference to this node.
	 */
	setOrder( value ) {

		this._order = value;

		return this;

	}

	/**
	 * Gets the Euler rotation order.
	 *
	 * @return {string} The Euler rotation order.
	 */
	getOrder() {

		return this._order;

	}

	/**
	 * The type of the {@link RotateNode#positionNode} defines the node's type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node's type.
	 */
	generateNodeType( builder ) {

		return this.positionNode.getNodeType( builder );

	}

	setup( builder ) {

		const { rotationNode, positionNode } = this;

		const nodeType = this.getNodeType( builder );

		if ( nodeType === 'vec2' ) {

			const cosAngle = rotationNode.cos();
			const sinAngle = rotationNode.sin();

			const rotationMatrix = mat2(
				cosAngle, sinAngle,
				sinAngle.negate(), cosAngle
			);

			return rotationMatrix.mul( positionNode );

		} else {

			const rotation = rotationNode;
			const order = this._order;

			const rotationXMatrix = mat4( vec4( 1.0, 0.0, 0.0, 0.0 ), vec4( 0.0, cos( rotation.x ), sin( rotation.x ), 0.0 ), vec4( 0.0, sin( rotation.x ).negate(), cos( rotation.x ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
			const rotationYMatrix = mat4( vec4( cos( rotation.y ), 0.0, sin( rotation.y ).negate(), 0.0 ), vec4( 0.0, 1.0, 0.0, 0.0 ), vec4( sin( rotation.y ), 0.0, cos( rotation.y ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
			const rotationZMatrix = mat4( vec4( cos( rotation.z ), sin( rotation.z ), 0.0, 0.0 ), vec4( sin( rotation.z ).negate(), cos( rotation.z ), 0.0, 0.0 ), vec4( 0.0, 0.0, 1.0, 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );

			const matrixMap = {
				'X': rotationXMatrix,
				'Y': rotationYMatrix,
				'Z': rotationZMatrix
			};

			const matrixChain = matrixMap[ order.charAt( 0 ) ]
				.mul( matrixMap[ order.charAt( 1 ) ] )
				.mul( matrixMap[ order.charAt( 2 ) ] );

			return matrixChain.mul( vec4( positionNode, 1.0 ) ).xyz;

		}

	}

	serialize( data ) {

		super.serialize( data );

		data.order = this._order;

	}

	deserialize( data ) {

		super.deserialize( data );

		this._order = data.order;

	}

}

/**
 * TSL function for creating a rotate node.
 *
 * @tsl
 * @function
 * @param {Node} positionNode - The position node.
 * @param {Node} rotationNode - Represents the rotation that is applied to the position node. Depending
 * on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value.
 * @param {string} [order='XYZ'] - The Euler rotation order. Only used for 3D rotation.
 * @returns {RotateNode}
 */
const rotate = /*@__PURE__*/ nodeProxy( RotateNode ).setParameterLength( 2, 3 );

/**
 * Replaces the default UV coordinates used in texture lookups.
 *
 * ```js
 *material.contextNode = replaceDefaultUV( ( textureNode ) => {
 *
 *	// ...
 *	return customUVCoordinates;
 *
 *} );
 *```
 *
 * @tsl
 * @function
 * @param {function(Node):Node<vec2>|Node<vec2>} callback - A callback that receives the texture node
 * and must return the new uv coordinates.
 * @param {Node} [node=null] - An optional node to which the context will be applied.
 * @return {ContextNode} A context node that replaces the default UV coordinates.
 */
function replaceDefaultUV( callback, node = null ) {

	const getUV = typeof callback === 'function' ? callback : () => callback;

	return context( node, { getUV } );

}

/**
 * Rotates the given uv coordinates around a center point
 *
 * @tsl
 * @function
 * @param {Node<vec2>} uv - The uv coordinates.
 * @param {Node<float>} rotation - The rotation defined in radians.
 * @param {Node<vec2>} center - The center of rotation
 * @return {Node<vec2>} The rotated uv coordinates.
 */
const rotateUV = /*@__PURE__*/ Fn( ( [ uv, rotation, center = vec2( 0.5 ) ] ) => {

	return rotate( uv.sub( center ), rotation ).add( center );

}, { uv: 'vec2', rotation: 'float', center: 'vec2', return: 'vec2' } );

/**
 * Applies a spherical warping effect to the given uv coordinates.
 *
 * @tsl
 * @function
 * @param {Node<vec2>} uv - The uv coordinates.
 * @param {Node<float>} strength - The strength of the effect.
 * @param {Node<vec2>} center - The center point
 * @return {Node<vec2>} The updated uv coordinates.
 */
const spherizeUV = /*@__PURE__*/ Fn( ( [ uv, strength, center = vec2( 0.5 ) ] ) => {

	const delta = uv.sub( center );
	const delta2 = delta.dot( delta );
	const delta4 = delta2.mul( delta2 );
	const deltaOffset = delta4.mul( strength );

	return uv.add( delta.mul( deltaOffset ) );

}, { uv: 'vec2', strength: 'float', center: 'vec2', return: 'vec2' } );

/**
 * This can be used to achieve a billboarding behavior for flat meshes. That means they are
 * oriented always towards the camera.
 *
 * ```js
 * material.vertexNode = billboarding();
 * ```
 *
 * @tsl
 * @function
 * @param {Object} config - The configuration object.
 * @param {?Node<vec3>} [config.position=null] - Can be used to define the billboard center position directly.
 * When null, the center is derived automatically from `positionWorld`.
 * @param {boolean} [config.horizontal=true] - Whether to follow the camera rotation horizontally or not.
 * @param {boolean} [config.vertical=false] - Whether to follow the camera rotation vertically or not.
 * @param {boolean} [config.horizontalRotation=false] - Whether to rotate around the Y axis to face the camera.
 * @return {Node<vec3>} The updated vertex position in clip space.
 */
const billboarding = /*@__PURE__*/ Fn( ( { position = null, horizontal = true, vertical = false, horizontalRotation = false } ) => {

	let center;

	if ( position !== null ) {

		center = nodeObject( position );

	} else {

		center = positionWorld.sub( modelWorldMatrix.mul( vec4( positionGeometry, 0 ) ).xyz );

	}

	const worldMatrix = modelWorldMatrix.toVar();
	worldMatrix[ 3 ][ 0 ] = center.x;
	worldMatrix[ 3 ][ 1 ] = center.y;
	worldMatrix[ 3 ][ 2 ] = center.z;

	const modelViewMatrix = cameraViewMatrix.mul( worldMatrix );

	const scaleX = modelWorldMatrix[ 0 ].length();
	const scaleY = modelWorldMatrix[ 1 ].length();
	const scaleZ = modelWorldMatrix[ 2 ].length();

	let right, up, forward;

	if ( defined( horizontalRotation ) ) {

		const worldPosition = worldMatrix[ 3 ].xyz;
		const look = cameraPosition.sub( worldPosition );
		const lookXZ = vec3( look.x, 0, look.z ).normalize();

		const right_w = vec3( lookXZ.z, 0, lookXZ.x.negate() );

		right = cameraViewMatrix.mul( vec4( right_w, 0 ) ).xyz.mul( scaleX );
		up = cameraViewMatrix[ 1 ].xyz.mul( scaleY );
		forward = cameraViewMatrix.mul( vec4( lookXZ, 0 ) ).xyz.mul( scaleZ );

	} else {

		if ( defined( horizontal ) ) right = vec3( scaleX, 0, 0 );
		if ( defined( vertical ) ) up = vec3( 0, scaleY, 0 );

		forward = vec3( 0, 0, 1 );

	}

	if ( right ) {

		modelViewMatrix[ 0 ][ 0 ] = right.x;
		modelViewMatrix[ 0 ][ 1 ] = right.y;
		modelViewMatrix[ 0 ][ 2 ] = right.z;

	}

	if ( up ) {

		modelViewMatrix[ 1 ][ 0 ] = up.x;
		modelViewMatrix[ 1 ][ 1 ] = up.y;
		modelViewMatrix[ 1 ][ 2 ] = up.z;

	}

	if ( forward ) {

		modelViewMatrix[ 2 ][ 0 ] = forward.x;
		modelViewMatrix[ 2 ][ 1 ] = forward.y;
		modelViewMatrix[ 2 ][ 2 ] = forward.z;

	}

	return cameraProjectionMatrix.mul( modelViewMatrix ).mul( positionGeometry );

} );

/**
 * A node that represents the dimensions of a texture. The texture size is
 * retrieved in the shader via built-in shader functions like `textureDimensions()`
 * or `textureSize()`.
 *
 * @augments Node
 */
class TextureSizeNode extends Node {

	static get type() {

		return 'TextureSizeNode';

	}

	/**
	 * Constructs a new texture size node.
	 *
	 * @param {TextureNode} textureNode - A texture node which size should be retrieved.
	 * @param {?Node<int>} [levelNode=null] - A level node which defines the requested mip.
	 */
	constructor( textureNode, levelNode = null ) {

		super( 'uvec2' );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isTextureSizeNode = true;

		/**
		 * A texture node which size should be retrieved.
		 *
		 * @type {TextureNode}
		 */
		this.textureNode = textureNode;

		/**
		 * A level node which defines the requested mip.
		 *
		 * @type {Node<int>}
		 * @default null
		 */
		this.levelNode = levelNode;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generate( builder ) {

		const textureProperty = this.textureNode.build( builder, 'property' );
		const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' );

		const snippet = builder.generateTextureSize( this.textureNode.value, textureProperty, level );

		return builder.format( snippet, this.getNodeType( builder ) );

	}

}

/**
 * TSL function for creating a texture size node.
 *
 * @tsl
 * @function
 * @param {TextureNode} textureNode - A texture node which size should be retrieved.
 * @param {?Node<int>} [levelNode=null] - A level node which defines the requested mip.
 * @returns {TextureSizeNode}
 */
const textureSize = /*@__PURE__*/ nodeProxy( TextureSizeNode ).setParameterLength( 1, 2 );

const EmptyTexture$1 = /*@__PURE__*/ new Texture();

/**
 * This type of uniform node represents a 2D texture.
 *
 * @augments UniformNode
 */
class TextureNode extends UniformNode {

	static get type() {

		return 'TextureNode';

	}

	/**
	 * Constructs a new texture node.
	 *
	 * @param {Texture} [value=EmptyTexture] - The texture.
	 * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
	 * @param {?Node<int>} [levelNode=null] - The level node.
	 * @param {?Node<float>} [biasNode=null] - The bias node.
	 */
	constructor( value = EmptyTexture$1, uvNode = null, levelNode = null, biasNode = null ) {

		super( value );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isTextureNode = true;

		/**
		 * Represents the texture coordinates.
		 *
		 * @type {?Node<vec2|vec3>}
		 * @default null
		 */
		this.uvNode = uvNode;

		/**
		 * Represents the mip level that should be selected.
		 *
		 * @type {?Node<int>}
		 * @default null
		 */
		this.levelNode = levelNode;

		/**
		 * Represents the bias to be applied during level-of-detail computation.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.biasNode = biasNode;

		/**
		 * Represents a reference value a texture sample is compared to.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.compareNode = null;

		/**
		 * When using texture arrays, the depth node defines the layer to select.
		 *
		 * @type {?Node<int>}
		 * @default null
		 */
		this.depthNode = null;

		/**
		 * When defined, a texture is sampled using explicit gradients.
		 *
		 * @type {?Array<Node<vec2>>}
		 * @default null
		 */
		this.gradNode = null;

		/**
		 * Represents the optional index constant of the channel to gather.
		 * This must be in range [0, 3] and a compile-time constant.
		 *
		 * @type {?Node<int>}
		 * @default null
		 */
		this.gatherNode = null;

		/**
		 * Represents the optional texel offset applied to the unnormalized texture
		 * coordinate before sampling the texture.
		 *
		 * @type {?Node<ivec2|ivec3>}
		 * @default null
		 */
		this.offsetNode = null;

		/**
		 * Whether texture values should be sampled or fetched.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.sampler = true;

		/**
		 * Whether the uv transformation matrix should be
		 * automatically updated or not. Use `setUpdateMatrix()`
		 * if you want to change the value of the property.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.updateMatrix = false;

		/**
		 * The reference node.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.referenceNode = null;

		/**
		 * The mergeable value is stored in a private property.
		 *
		 * @private
		 * @type {boolean}
		 * @default true
		 */
		this._mergeable = true;

		/**
		 * The texture value is stored in a private property.
		 *
		 * @private
		 * @type {Texture}
		 */
		this._value = value;

		/**
		 * The uniform node that represents the uv transformation matrix.
		 *
		 * @private
		 * @type {?UniformNode<mat3>}
		 * @default null
		 */
		this._matrixUniform = null;

		/**
		 * The uniform node that represents the y-flip. Only required for WebGL.
		 *
		 * @private
		 * @type {?UniformNode<bool>}
		 * @default null
		 */
		this._flipYUniform = null;

		/**
		 * Whether the node is used as a comparison sampler, e.g. via `samplerComparison()`.
		 *
		 * @private
		 * @type {boolean}
		 * @default false
		 */
		this._samplerComparison = false;

		this.setUpdateMatrix( uvNode === null );

	}

	set mergeable( value ) {

		if ( this.referenceNode ) {

			this.referenceNode.mergeable = value;

		} else {

			this._mergeable = value;

		}

	}

	/**
	 * Whether the uniform may be merged with other uniforms at compile-time.
	 *
	 * @type {boolean}
	 * @default true
	 */
	get mergeable() {

		return this.referenceNode ? this.referenceNode.mergeable : this._mergeable;

	}

	set value( value ) {

		if ( this.referenceNode ) {

			this.referenceNode.value = value;

		} else {

			this._value = value;

		}

	}

	/**
	 * The texture value.
	 *
	 * @type {Texture}
	 */
	get value() {

		return this.referenceNode ? this.referenceNode.value : this._value;

	}

	/**
	 * Overwritten since the uniform hash is defined by the texture's UUID - if mergeable - and the hash of its reference node otherwise.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The uniform hash.
	 */
	getUniformHash( builder ) {

		if ( this.referenceNode && ! this.referenceNode.mergeable ) {

			return this.referenceNode.getHash( builder );

		}

		if ( ! this._mergeable ) {

			return this.getHash( builder );

		}

		return this.value.uuid;

	}

	/**
	 * Overwritten since the texture's type and filters influence the generated shader code.
	 *
	 * @return {number} The custom cache key.
	 */
	customCacheKey() {

		const { type, minFilter, magFilter } = this.value;

		return hash$1( this.id, type, minFilter, magFilter );

	}

	/**
	 * Overwritten since the node type is inferred from the texture type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( /*builder*/ ) {

		if ( this.gatherNode !== null ) {

			return 'vec4';

		}

		return getTextureType( this.value );

	}

	/**
	 * Overwrites the default implementation to return a fixed value `'texture'`.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( /*builder*/ ) {

		return 'texture';

	}

	/**
	 * Returns a default uvs based on the current texture's channel.
	 *
	 * @return {AttributeNode<vec2>} The default uvs.
	 */
	getDefaultUV() {

		return uv$1( this.value.channel );

	}

	/**
	 * Overwritten to always return the texture reference of the node.
	 *
	 * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type.
	 * @return {Texture} The texture reference.
	 */
	updateReference( /*state*/ ) {

		return this.value;

	}

	/**
	 * Transforms the given uv node with the texture transformation matrix.
	 *
	 * @param {Node} uvNode - The uv node to transform.
	 * @return {Node} The transformed uv node.
	 */
	getTransformedUV( uvNode ) {

		return this._getMatrixUniform().mul( vec3( uvNode, 1 ) ).xy;

	}

	/**
	 * Transforms the given uv gradient with the linear part of the texture matrix.
	 *
	 * @private
	 * @param {Node<vec2>} gradientNode - The uv gradient.
	 * @return {Node<vec2>} The transformed gradient.
	 */
	_getTransformedGradient( gradientNode ) {

		return this._getMatrixUniform().mul( vec3( gradientNode, 0 ) ).xy;

	}

	/**
	 * Returns the uniform of the texture matrix shared by all nodes of the same base texture node.
	 *
	 * @private
	 * @return {UniformNode<mat3>} The texture matrix uniform.
	 */
	_getMatrixUniform() {

		const baseNode = this.getBase();

		if ( baseNode._matrixUniform === null ) {

			baseNode._matrixUniform = uniform( baseNode.value.matrix ).onObjectUpdate( () => {

				const texture = baseNode.value;

				if ( texture.matrixAutoUpdate === true ) texture.updateMatrix();

				return texture.matrix;

			} );

		}

		return baseNode._matrixUniform;

	}

	/**
	 * Defines whether the uv transformation matrix should automatically be updated or not.
	 *
	 * @param {boolean} value - The update toggle.
	 * @return {TextureNode} A reference to this node.
	 */
	setUpdateMatrix( value ) {

		this.updateMatrix = value;

		return this;

	}

	/**
	 * Setups the uv node. Depending on the backend as well as texture's image and type, it might be necessary
	 * to modify the uv node for correct sampling.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node} uvNode - The uv node to setup.
	 * @return {Node} The updated uv node.
	 */
	setupUV( builder, uvNode ) {

		if ( builder.isFlipY() ) {

			const baseNode = this.getBase();

			if ( baseNode._flipYUniform === null ) {

				baseNode._flipYUniform = uniform( false ).onObjectUpdate( () => {

					const texture = baseNode.value;

					return ( texture.image instanceof ImageBitmap && texture.flipY === true ) || texture.isRenderTargetTexture === true || texture.isFramebufferTexture === true || texture.isDepthTexture === true;

				} );

			}

			uvNode = uvNode.toVar();

			if ( this.sampler ) {

				uvNode = baseNode._flipYUniform.select( uvNode.flipY(), uvNode );

			} else {

				uvNode = baseNode._flipYUniform.select( uvNode.setY( int( textureSize( this, this.levelNode ).y ).sub( int( uvNode.y ) ).sub( 1 ) ), uvNode );

			}

		}

		return uvNode;

	}

	/**
	 * Setups texture node by preparing the internal nodes for code generation.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setup( builder ) {

		const properties = builder.getNodeProperties( this );
		properties.referenceNode = this.referenceNode;

		//

		const texture = this.value;

		if ( ! texture || texture.isTexture !== true ) {

			throw new NodeError( 'THREE.TSL: `texture( value )` function expects a valid instance of THREE.Texture().', this.stackTrace );

		}

		//

		const uvNode = Fn( () => {

			let uvNode = this.uvNode;

			if ( ( uvNode === null || builder.context.forceUVContext === true ) && builder.context.getUV ) {

				uvNode = builder.context.getUV( this, builder );

			}

			if ( ! uvNode ) uvNode = this.getDefaultUV();

			if ( this.updateMatrix === true ) {

				uvNode = this.getTransformedUV( uvNode );

			}

			uvNode = this.setupUV( builder, uvNode );

			return uvNode;

		} )();

		//

		let levelNode = this.levelNode;

		if ( levelNode === null && builder.context.getTextureLevel ) {

			levelNode = builder.context.getTextureLevel( this );

		}

		//

		let compareNode = null;
		let compareStepNode = null;

		if ( this.compareNode !== null ) {

			if ( builder.renderer.hasCompatibility( Compatibility.TEXTURE_COMPARE ) ) {

				compareNode = this.compareNode;

			} else {

				const compareFunction = texture.compareFunction;

				if ( compareFunction === null || compareFunction === LessCompare || compareFunction === LessEqualCompare || compareFunction === GreaterCompare || compareFunction === GreaterEqualCompare ) {

					compareStepNode = this.compareNode;

				} else {

					compareNode = this.compareNode;

					warnOnce( 'TSL: Only "LessCompare", "LessEqualCompare", "GreaterCompare" and "GreaterEqualCompare" are supported for depth texture comparison fallback.' );

				}

			}

		}

		// the context can provide analytic gradients of the default uv, e.g. if the uv is reconstructed per fragment

		let gradNode = this.gradNode;

		if ( gradNode === null && this.uvNode === null && levelNode === null && this.biasNode === null && this.sampler === true && builder.context.getTextureGradient ) {

			const gradient = builder.context.getTextureGradient( this, builder );

			if ( gradient ) {

				gradNode = this.updateMatrix === true ? gradient.map( ( node ) => this._getTransformedGradient( node ) ) : gradient;

			}

		}

		properties.uvNode = uvNode;
		properties.levelNode = levelNode;
		properties.biasNode = this.biasNode;
		properties.compareNode = compareNode;
		properties.compareStepNode = compareStepNode;
		properties.gradNode = gradNode;
		properties.gatherNode = this.gatherNode;
		properties.depthNode = this.depthNode;
		properties.offsetNode = this.offsetNode;

	}

	/**
	 * Generates the uv code snippet.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node} uvNode - The uv node to generate code for.
	 * @return {string} The generated code snippet.
	 */
	generateUV( builder, uvNode ) {

		return uvNode.build( builder, this.sampler === true ? 'vec2' : 'ivec2' );

	}

	/**
	 * Generates the offset code snippet.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node} offsetNode - The offset node to generate code for.
	 * @return {string} The generated code snippet.
	 */
	generateOffset( builder, offsetNode ) {

		return offsetNode.build( builder, 'ivec2' );

	}

	/**
	 * Generates the snippet for the texture sampling.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} textureProperty - The texture property.
	 * @param {string} uvSnippet - The uv snippet.
	 * @param {?string} levelSnippet - The level snippet.
	 * @param {?string} biasSnippet - The bias snippet.
	 * @param {?string} depthSnippet - The depth snippet.
	 * @param {?string} compareSnippet - The compare snippet.
	 * @param {?Array<string>} gradSnippet - The grad snippet.
	 * @param {?string} gatherSnippet - The gather snippet.
	 * @param {?string} offsetSnippet - The offset snippet.
	 * @param {?string} flipYSnippet - The y-flip snippet. Only used for WebGL.
	 * @return {string} The generated code snippet.
	 */
	generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet, gatherSnippet, offsetSnippet, flipYSnippet ) {

		const texture = this.value;

		let snippet;

		if ( biasSnippet ) {

			snippet = builder.generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet, offsetSnippet );

		} else if ( gradSnippet ) {

			snippet = builder.generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet, offsetSnippet );

		} else if ( gatherSnippet ) {

			if ( compareSnippet ) {

				snippet = builder.generateTextureGatherCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet, flipYSnippet );

			} else {

				snippet = builder.generateTextureGather( texture, textureProperty, uvSnippet, gatherSnippet, depthSnippet, offsetSnippet, flipYSnippet );

			}

		} else if ( compareSnippet ) {

			snippet = builder.generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet );

		} else if ( this.sampler === false ) {

			snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet );

		} else if ( levelSnippet ) {

			snippet = builder.generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet );

		} else {

			snippet = builder.generateTexture( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet );

		}

		return snippet;

	}

	/**
	 * Generates the code snippet of the texture node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} output - The current output.
	 * @return {string} The generated code snippet.
	 */
	generate( builder, output ) {

		const texture = this.value;

		const properties = builder.getNodeProperties( this );
		const textureProperty = super.generate( builder, 'property' );

		if ( output === 'samplerComparison' || properties.compareNode !== null ) {

			// make sure the texture node creates a binding with a comparison sampler if necessary

			this.getSharedNode( builder )._samplerComparison = true;

		}

		if ( /^sampler/.test( output ) ) {

			return textureProperty + '_sampler';

		} else if ( builder.isReference( output ) ) {

			return textureProperty;

		} else {

			const nodeData = builder.getDataFromNode( this );

			const nodeType = this.getNodeType( builder );

			let propertyName = nodeData.propertyName;

			if ( propertyName === undefined ) {

				const { uvNode, levelNode, biasNode, compareNode, compareStepNode, depthNode, gradNode, gatherNode, offsetNode } = properties;

				const uvSnippet = this.generateUV( builder, uvNode );
				const levelSnippet = levelNode ? levelNode.build( builder, 'float' ) : null;
				const biasSnippet = biasNode ? biasNode.build( builder, 'float' ) : null;
				const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null;
				const compareSnippet = compareNode ? compareNode.build( builder, 'float' ) : null;
				const compareStepSnippet = compareStepNode ? compareStepNode.build( builder, 'float' ) : null;
				const gradSnippet = gradNode ? [ gradNode[ 0 ].build( builder, 'vec2' ), gradNode[ 1 ].build( builder, 'vec2' ) ] : null;
				const gatherSnippet = gatherNode ? gatherNode.build( builder, 'int' ) : null;
				const offsetSnippet = offsetNode ? this.generateOffset( builder, offsetNode ) : null;

				const flipYUniform = this.getBase()._flipYUniform;
				const flipYSnippet = flipYUniform ? flipYUniform.build( builder, 'bool' ) : null;

				let finalDepthSnippet = depthSnippet;

				if ( finalDepthSnippet === null && texture.isArrayTexture && this.isTexture3DNode !== true ) {

					finalDepthSnippet = '0';

				}

				const nodeVar = builder.getVarFromNode( this );

				propertyName = builder.getPropertyName( nodeVar );

				let snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, finalDepthSnippet, compareSnippet, gradSnippet, gatherSnippet, offsetSnippet, flipYSnippet );

				let snippetType;

				if ( texture.isDepthTexture === true && gatherSnippet === null ) {

					snippetType = 'float';

				} else {

					snippetType = texture.type === UnsignedIntType ? 'uvec4' : ( texture.type === IntType ? 'ivec4' : 'vec4' );

				}

				snippet = builder.format( snippet, snippetType, nodeType );

				if ( compareStepSnippet !== null ) {

					const compareFunction = texture.compareFunction;

					if ( compareFunction === GreaterCompare || compareFunction === GreaterEqualCompare ) {

						snippet = step( expression( snippet, nodeType ), expression( compareStepSnippet, 'float' ) ).build( builder, nodeType );

					} else {

						snippet = step( expression( compareStepSnippet, 'float' ), expression( snippet, nodeType ) ).build( builder, nodeType );

					}

				}

				builder.addLineFlowCode( `${propertyName} = ${snippet}`, this );

				nodeData.snippet = snippet;
				nodeData.propertyName = propertyName;

			}

			let snippet = propertyName;

			if ( builder.needsToWorkingColorSpace( texture ) ) {

				snippet = colorSpaceToWorking( expression( snippet, nodeType ), texture.colorSpace ).setup( builder ).build( builder, nodeType );

			}

			return builder.format( snippet, nodeType, output );

		}

	}

	/**
	 * Sets the sampler value.
	 *
	 * @param {boolean} value - The sampler value to set.
	 * @return {TextureNode} A reference to this texture node.
	 */
	setSampler( value ) {

		this.sampler = value;

		return this;

	}

	/**
	 * Returns the sampler value.
	 *
	 * @return {boolean} The sampler value.
	 */
	getSampler() {

		return this.sampler;

	}

	// @TODO: Move to TSL

	/**
	 * Samples the texture with the given uv node.
	 *
	 * @param {Node} uvNode - The uv node.
	 * @return {TextureNode} A texture node representing the texture sample.
	 */
	sample( uvNode ) {

		const textureNode = this.clone();
		textureNode.uvNode = nodeObject( uvNode );
		textureNode.referenceNode = this.getBase();

		return textureNode;

	}

	/**
	 * TSL function for creating a texture node that fetches/loads texels without interpolation.
	 *
	 * @param {Node<uvec2>} uvNode - The uv node.
	 * @returns {TextureNode} A texture node representing the texture load.
	 */
	load( uvNode ) {

		return this.sample( uvNode ).setSampler( false );

	}

	/**
	 * Samples a blurred version of the texture by defining an internal bias.
	 *
	 * @param {Node<float>} amountNode - How blurred the texture should be.
	 * @return {TextureNode} A texture node representing the texture sample.
	 */
	blur( amountNode ) {

		const textureNode = this.clone();
		textureNode.biasNode = nodeObject( amountNode ).mul( maxMipLevel( textureNode ) );
		textureNode.referenceNode = this.getBase();

		const map = textureNode.value;

		if ( textureNode.generateMipmaps === false && ( map && map.generateMipmaps === false || map.minFilter === NearestFilter || map.magFilter === NearestFilter ) ) {

			warn( 'TSL: texture().blur() requires mipmaps and sampling. Use .generateMipmaps=true and .minFilter/.magFilter=THREE.LinearFilter in the Texture.' );

			textureNode.biasNode = null;

		}

		return textureNode;

	}

	/**
	 * Samples a specific mip of the texture.
	 *
	 * @param {Node<int>} levelNode - The mip level to sample.
	 * @return {TextureNode} A texture node representing the texture sample.
	 */
	level( levelNode ) {

		const textureNode = this.clone();
		textureNode.levelNode = nodeObject( levelNode );
		textureNode.referenceNode = this.getBase();

		return textureNode;

	}

	/**
	 * Returns the texture size of the requested level.
	 *
	 * @param {Node<int>} levelNode - The level to compute the size for.
	 * @return {TextureSizeNode} The texture size.
	 */
	size( levelNode ) {

		return textureSize( this, levelNode );

	}

	/**
	 * Samples the texture with the given bias.
	 *
	 * @param {Node<float>} biasNode - The bias node.
	 * @return {TextureNode} A texture node representing the texture sample.
	 */
	bias( biasNode ) {

		const textureNode = this.clone();
		textureNode.biasNode = nodeObject( biasNode );
		textureNode.referenceNode = this.getBase();

		return textureNode;

	}

	/**
	 * Returns the base texture of this node.
	 * @return {TextureNode} The base texture node.
	 */
	getBase() {

		return this.referenceNode ? this.referenceNode.getBase() : this;

	}

	/**
	 * Samples the texture by executing a compare operation.
	 *
	 * @param {Node<float>} compareNode - The node that defines the compare value.
	 * @return {TextureNode} A texture node representing the texture sample.
	 */
	compare( compareNode ) {

		const textureNode = this.clone();
		textureNode.compareNode = nodeObject( compareNode );
		textureNode.referenceNode = this.getBase();

		return textureNode;

	}

	/**
	 * Samples the texture using an explicit gradient.
	 *
	 * @param {Node<vec2>} gradNodeX - The gradX node.
	 * @param {Node<vec2>} gradNodeY - The gradY node.
	 * @return {TextureNode} A texture node representing the texture sample.
	 */
	grad( gradNodeX, gradNodeY ) {

		const textureNode = this.clone();
		textureNode.gradNode = [ nodeObject( gradNodeX ), nodeObject( gradNodeY ) ];
		textureNode.referenceNode = this.getBase();

		return textureNode;

	}

	/**
	 * Gathers four texels from the texture.
	 *
	 * @param {Node<int>} gatherNode - The index of the channel to read. This must be in range [0, 3] and a compile-time constant.
	 * @return {TextureNode} A texture node representing the texture sample.
	 */
	gather( gatherNode = 0 ) {

		const textureNode = this.clone();
		textureNode.gatherNode = nodeObject( gatherNode );
		textureNode.referenceNode = this.getBase();

		return textureNode;

	}

	/**
	 * Returns `true` if the texture is sampled with a depth comparison,
	 * meaning the node must be bound with a comparison sampler.
	 *
	 * @return {boolean} Whether comparison sampling is used or not.
	 */
	isSampleCompare() {

		return this.compareNode !== null || this._samplerComparison === true;

	}

	/**
	 * Samples the texture by defining a depth node.
	 *
	 * @param {Node<int>} depthNode - The depth node.
	 * @return {TextureNode} A texture node representing the texture sample.
	 */
	depth( depthNode ) {

		const textureNode = this.clone();
		textureNode.depthNode = nodeObject( depthNode );
		textureNode.referenceNode = this.getBase();

		return textureNode;

	}

	/**
	 * Samples the texture by defining an offset node.
	 *
	 * @param {Node<ivec2>} offsetNode - The offset node.
	 * @return {TextureNode} A texture node representing the texture sample.
	 */
	offset( offsetNode ) {

		const textureNode = this.clone();
		textureNode.offsetNode = nodeObject( offsetNode );
		textureNode.referenceNode = this.getBase();

		return textureNode;

	}

	// --

	serialize( data ) {

		super.serialize( data );

		data.value = this.value.toJSON( data.meta ).uuid;
		data.sampler = this.sampler;
		data.updateMatrix = this.updateMatrix;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.value = data.meta.textures[ data.value ];
		this.sampler = data.sampler;
		this.updateMatrix = data.updateMatrix;

	}

	/**
	 * Clones the texture node.
	 *
	 * @return {TextureNode} The cloned texture node.
	 */
	clone() {

		const newNode = new this.constructor( this.value, this.uvNode, this.levelNode, this.biasNode );
		newNode.sampler = this.sampler;
		newNode.depthNode = this.depthNode;
		newNode.compareNode = this.compareNode;
		newNode.gradNode = this.gradNode;
		newNode.gatherNode = this.gatherNode;
		newNode.offsetNode = this.offsetNode;

		return newNode;

	}

}

/**
 * TSL function for creating a texture node.
 *
 * @tsl
 * @function
 * @param {?Texture} value - The texture.
 * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
 * @param {?Node<int>} [levelNode=null] - The level node.
 * @param {?Node<float>} [biasNode=null] - The bias node.
 * @returns {TextureNode}
 */
const textureBase = /*@__PURE__*/ nodeProxy( TextureNode ).setParameterLength( 1, 4 ).setName( 'texture' );

/**
 * TSL function for creating a texture node or sample a texture node already existing.
 *
 * @tsl
 * @function
 * @param {?(Texture|TextureNode)} [value=EmptyTexture] - The texture.
 * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
 * @param {?Node<int>} [levelNode=null] - The level node.
 * @param {?Node<float>} [biasNode=null] - The bias node.
 * @returns {TextureNode}
 */
const texture = ( value = EmptyTexture$1, uvNode = null, levelNode = null, biasNode = null ) => {

	let textureNode;

	if ( value && value.isTextureNode === true ) {

		textureNode = value.clone();
		textureNode.referenceNode = value.getBase(); // Ensure the reference is set to the original node

		if ( uvNode !== null ) textureNode.uvNode = nodeObject( uvNode );
		if ( levelNode !== null ) textureNode.levelNode = nodeObject( levelNode );
		if ( biasNode !== null ) textureNode.biasNode = nodeObject( biasNode );

	} else {

		textureNode = textureBase( value, uvNode, levelNode, biasNode );

	}

	return textureNode;

};

/**
 * TSL function for creating a non-mergeable uniform texture node.
 *
 * @tsl
 * @function
 * @param {?Texture} value - The texture.
 * @returns {TextureNode}
 */
const uniformTexture = ( value = EmptyTexture$1 ) => {

	const textureNode = texture( value );

	textureNode.mergeable = false;

	return textureNode;

};

/**
 * TSL function for creating a texture node that fetches/loads texels without interpolation.
 *
 * @tsl
 * @function
 * @param {?(Texture|TextureNode)} [value=EmptyTexture] - The texture.
 * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
 * @param {?Node<int>} [levelNode=null] - The level node.
 * @param {?Node<float>} [biasNode=null] - The bias node.
 * @returns {TextureNode}
 */
const textureLoad = ( ...params ) => texture( ...params ).setSampler( false );

const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level );

/**
 * Converts a texture or texture node to a sampler.
 *
 * @tsl
 * @function
 * @param {TextureNode|Texture} value - The texture or texture node to convert.
 * @returns {Node}
 */
const sampler = ( value ) => ( value.isNode === true ? value : texture( value ) ).convert( 'sampler' );

/**
 * Converts a texture or texture node to a sampler comparison.
 *
 * @tsl
 * @function
 * @param {TextureNode|Texture} value - The texture or texture node to convert.
 * @returns {Node}
 */
const samplerComparison = ( value ) => ( value.isNode === true ? value : texture( value ) ).convert( 'samplerComparison' );

const _size$3 = /*@__PURE__*/ new Vector2();

/**
 * A special type of texture node which represents the data of the current viewport
 * as a texture. The module extracts data from the current bound framebuffer with
 * a copy operation so no extra render pass is required to produce the texture data
 * (which is good for performance). `ViewportTextureNode` can be used as an input for a
 * variety of effects like refractive or transmissive materials.
 *
 * @augments TextureNode
 */
class ViewportTextureNode extends TextureNode {

	static get type() {

		return 'ViewportTextureNode';

	}

	/**
	 * Constructs a new viewport texture node.
	 *
	 * @param {Node} [uvNode=screenUV] - The uv node.
	 * @param {?Node} [levelNode=null] - The level node.
	 * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically.
	 */
	constructor( uvNode = screenUV, levelNode = null, framebufferTexture = null ) {

		let defaultFramebuffer = null;

		if ( framebufferTexture === null ) {

			defaultFramebuffer = new FramebufferTexture();
			defaultFramebuffer.minFilter = LinearMipmapLinearFilter;

			framebufferTexture = defaultFramebuffer;

		} else {

			defaultFramebuffer = framebufferTexture;

		}

		super( framebufferTexture, uvNode, levelNode );

		/**
		 * Whether to generate mipmaps or not.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.generateMipmaps = false;

		/**
		 * The reference framebuffer texture. This is used to store the framebuffer texture
		 * for the current render target. If the render target changes, a new framebuffer texture
		 * is created automatically.
		 *
		 * @type {FramebufferTexture}
		 * @default null
		 */
		this.defaultFramebuffer = defaultFramebuffer;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isOutputTextureNode = true;

		/**
		 * The `updateBeforeType` is set to `NodeUpdateType.RENDER` since the node should extract
		 * the current contents of the bound framebuffer for each render call.
		 *
		 * @type {string}
		 * @default 'render'
		 */
		this.updateBeforeType = NodeUpdateType.RENDER;

		/**
		 * The framebuffer texture for the current renderer context.
		 *
		 * @type {WeakMap<RenderTarget, FramebufferTexture>}
		 * @private
		 */
		this._cacheTextures = new WeakMap();

	}

	/**
	 * This methods returns a texture for the given render target or canvas target reference.
	 *
	 * To avoid rendering errors, `ViewportTextureNode` must use unique framebuffer textures
	 * for different render contexts.
	 *
	 * @param {?(RenderTarget|CanvasTarget)} [reference=null] - The render target or canvas target reference.
	 * @return {Texture} The framebuffer texture.
	 */
	getTextureForReference( reference = null ) {

		let defaultFramebuffer;
		let cacheTextures;

		if ( this.referenceNode ) {

			defaultFramebuffer = this.referenceNode.defaultFramebuffer;
			cacheTextures = this.referenceNode._cacheTextures;

		} else {

			defaultFramebuffer = this.defaultFramebuffer;
			cacheTextures = this._cacheTextures;

		}

		if ( reference === null ) {

			return defaultFramebuffer;

		}

		if ( cacheTextures.has( reference ) === false ) {

			const framebufferTexture = defaultFramebuffer.clone();

			cacheTextures.set( reference, framebufferTexture );

		}

		return cacheTextures.get( reference );

	}

	updateReference( frame ) {

		const renderer = frame.renderer;
		const renderTarget = renderer.getRenderTarget();
		const canvasTarget = renderer.getCanvasTarget();

		const reference = renderTarget ? renderTarget : canvasTarget;

		this.value = this.getTextureForReference( reference );

		return this.value;

	}

	updateBefore( frame ) {

		const renderer = frame.renderer;
		const renderTarget = renderer.getRenderTarget();
		const canvasTarget = renderer.getCanvasTarget();

		const reference = renderTarget ? renderTarget : canvasTarget;

		if ( reference === null ) {

			renderer.getDrawingBufferSize( _size$3 );

		} else if ( reference.getDrawingBufferSize ) {

			reference.getDrawingBufferSize( _size$3 );

		} else {

			_size$3.set( reference.width, reference.height );

		}

		//

		const framebufferTexture = this.getTextureForReference( reference );

		if ( framebufferTexture.image.width !== _size$3.width || framebufferTexture.image.height !== _size$3.height ) {

			framebufferTexture.image.width = _size$3.width;
			framebufferTexture.image.height = _size$3.height;
			framebufferTexture.needsUpdate = true;

		}

		//

		const currentGenerateMipmaps = framebufferTexture.generateMipmaps;
		framebufferTexture.generateMipmaps = this.generateMipmaps;

		renderer.copyFramebufferToTexture( framebufferTexture );

		framebufferTexture.generateMipmaps = currentGenerateMipmaps;

	}

	clone() {

		const viewportTextureNode = new this.constructor( this.uvNode, this.levelNode, this.value );
		viewportTextureNode.generateMipmaps = this.generateMipmaps;

		return viewportTextureNode;

	}

}

/**
 * TSL function for creating a viewport texture node.
 *
 * @tsl
 * @function
 * @param {?Node} [uvNode=screenUV] - The uv node.
 * @param {?Node} [levelNode=null] - The level node.
 * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically.
 * @returns {ViewportTextureNode}
 */
const viewportTexture = /*@__PURE__*/ nodeProxy( ViewportTextureNode ).setParameterLength( 0, 3 );

/**
 * TSL function for creating a viewport texture node with enabled mipmap generation.
 *
 * @tsl
 * @function
 * @param {?Node} [uvNode=screenUV] - The uv node.
 * @param {?Node} [levelNode=null] - The level node.
 * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically.
 * @returns {ViewportTextureNode}
 */
const viewportMipTexture = /*@__PURE__*/ nodeProxy( ViewportTextureNode, null, null, { generateMipmaps: true } ).setParameterLength( 0, 3 );

// Singleton instances for common usage
const _singletonOpaqueViewportTextureNode = /*@__PURE__*/ viewportMipTexture();

/**
 * TSL function for creating a viewport texture node with enabled mipmap generation.
 * The texture should only contain the opaque rendering objects.
 *
 * This should be used just in transparent or transmissive materials.
 *
 * @tsl
 * @function
 * @param {?Node} [uv=screenUV] - The uv node.
 * @param {?Node} [level=null] - The level node.
 * @returns {ViewportTextureNode}
 */
const viewportOpaqueMipTexture = ( uv = screenUV, level = null ) => _singletonOpaqueViewportTextureNode.sample( uv, level ); // TODO: Use once() when sample() supports it

let _sharedDepthbuffer = null;

/**
 * Represents the depth of the current viewport as a texture. This module
 * can be used in combination with viewport texture to achieve effects
 * that require depth evaluation.
 *
 * @augments ViewportTextureNode
 */
class ViewportDepthTextureNode extends ViewportTextureNode {

	static get type() {

		return 'ViewportDepthTextureNode';

	}

	/**
	 * Constructs a new viewport depth texture node.
	 *
	 * @param {Node} [uvNode=screenUV] - The uv node.
	 * @param {?Node} [levelNode=null] - The level node.
	 * @param {?DepthTexture} [depthTexture=null] - A depth texture. If not provided, uses a shared depth texture.
	 */
	constructor( uvNode = screenUV, levelNode = null, depthTexture = null ) {

		if ( depthTexture === null ) {

			if ( _sharedDepthbuffer === null ) {

				_sharedDepthbuffer = new DepthTexture();

			}

			depthTexture = _sharedDepthbuffer;

		}

		super( uvNode, levelNode, depthTexture );

	}

}

/**
 * TSL function for a viewport depth texture node.
 *
 * @tsl
 * @function
 * @param {?Node} [uvNode=screenUV] - The uv node.
 * @param {?Node} [levelNode=null] - The level node.
 * @param {?DepthTexture} [depthTexture=null] - A depth texture. If not provided, a depth texture is created automatically.
 * @returns {ViewportDepthTextureNode}
 */
const viewportDepthTexture = /*@__PURE__*/ nodeProxy( ViewportDepthTextureNode ).setParameterLength( 0, 3 );

/**
 * This node offers a collection of features in context of the depth logic in the fragment shader.
 * Depending on {@link ViewportDepthNode#scope}, it can be used to define a depth value for the current
 * fragment or for depth evaluation purposes.
 *
 * @augments Node
 */
class ViewportDepthNode extends Node {

	static get type() {

		return 'ViewportDepthNode';

	}

	/**
	 * Constructs a new viewport depth node.
	 *
	 * @param {('depth'|'depthBase'|'linearDepth')} scope - The node's scope.
	 * @param {?Node} [valueNode=null] - The value node.
	 */
	constructor( scope, valueNode = null ) {

		super( 'float' );

		/**
		 * The node behaves differently depending on which scope is selected.
		 *
		 * - `ViewportDepthNode.DEPTH_BASE`: Allows to define a value for the current fragment's depth.
		 * - `ViewportDepthNode.DEPTH`: Represents the depth value for the current fragment (`valueNode` is ignored).
		 * - `ViewportDepthNode.LINEAR_DEPTH`: Represents the linear (orthographic) depth value of the current fragment.
		 * If a `valueNode` is set, the scope can be used to convert perspective depth data to linear data.
		 *
		 * @type {('depth'|'depthBase'|'linearDepth')}
		 */
		this.scope = scope;

		/**
		 * Can be used to define a custom depth value.
		 * The property is ignored in the `ViewportDepthNode.DEPTH` scope.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.valueNode = valueNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isViewportDepthNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generate( builder ) {

		const { scope } = this;

		if ( scope === ViewportDepthNode.DEPTH_BASE ) {

			return builder.getFragDepth();

		}

		return super.generate( builder );

	}

	setup( { camera } ) {

		const { scope } = this;
		const value = this.valueNode;

		let node = null;

		if ( scope === ViewportDepthNode.DEPTH_BASE ) {

			if ( value !== null ) {

				node = depthBase().assign( value );

			}

		} else if ( scope === ViewportDepthNode.DEPTH ) {

			if ( camera.isOrthographicCamera !== true ) {

				node = viewZToPerspectiveDepth( positionView.z, cameraNear, cameraFar );

			} else {

				node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );

			}

		} else if ( scope === ViewportDepthNode.LINEAR_DEPTH ) {

			if ( value !== null ) {

				if ( camera.isOrthographicCamera !== true ) {

					const viewZ = perspectiveDepthToViewZ( value, cameraNear, cameraFar );

					node = viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );

				} else {

					node = value;

				}

			} else {

				node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );

			}

		}

		return node;

	}

}

ViewportDepthNode.DEPTH_BASE = 'depthBase';
ViewportDepthNode.DEPTH = 'depth';
ViewportDepthNode.LINEAR_DEPTH = 'linearDepth';

// NOTE: viewZ, the z-coordinate in camera space, is negative for points in front of the camera

/**
 * TSL function for converting a viewZ value to an orthographic depth value.
 *
 * @tsl
 * @function
 * @param {Node<float>} viewZ - The viewZ node.
 * @param {Node<float>} near - The camera's near value.
 * @param {Node<float>} far - The camera's far value.
 * @returns {Node<float>}
 */
const viewZToOrthographicDepth = ( viewZ, near, far ) => viewZ.add( near ).div( near.sub( far ) );

/**
 * TSL function for converting a viewZ value to a reversed orthographic depth value.
 *
 * @tsl
 * @function
 * @param {Node<float>} viewZ - The viewZ node.
 * @param {Node<float>} near - The camera's near value.
 * @param {Node<float>} far - The camera's far value.
 * @returns {Node<float>}
 */
const viewZToReversedOrthographicDepth = ( viewZ, near, far ) => viewZ.add( far ).div( far.sub( near ) );

/**
 * TSL function for converting an orthographic depth value to a viewZ value.
 *
 * @tsl
 * @function
 * @param {Node<float>} depth - The orthographic depth.
 * @param {Node<float>} near - The camera's near value.
 * @param {Node<float>} far - The camera's far value.
 * @returns {Node<float>}
 */
const orthographicDepthToViewZ = /*@__PURE__*/ Fn( ( [ depth, near, far ], builder ) => {

	if ( builder.renderer.reversedDepthBuffer === true ) {

		return far.sub( near ).mul( depth ).sub( far );

	} else {

		return near.sub( far ).mul( depth ).sub( near );

	}

} );

/**
 * TSL function for converting a viewZ value to a perspective depth value.
 *
 * Note: {link https://twitter.com/gonnavis/status/1377183786949959682}.
 *
 * @tsl
 * @function
 * @param {Node<float>} viewZ - The viewZ node.
 * @param {Node<float>} near - The camera's near value.
 * @param {Node<float>} far - The camera's far value.
 * @returns {Node<float>}
 */
const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ).mul( far ).div( far.sub( near ).mul( viewZ ) );

/**
 * TSL function for converting a viewZ value to a reversed perspective depth value.
 *
 * @tsl
 * @function
 * @param {Node<float>} viewZ - The viewZ node.
 * @param {Node<float>} near - The camera's near value.
 * @param {Node<float>} far - The camera's far value.
 * @returns {Node<float>}
 */
const viewZToReversedPerspectiveDepth = ( viewZ, near, far ) => near.mul( viewZ.add( far ) ).div( viewZ.mul( near.sub( far ) ) );

/**
 * TSL function for converting a perspective depth value to a viewZ value.
 *
 * @tsl
 * @function
 * @param {Node<float>} depth - The perspective depth.
 * @param {Node<float>} near - The camera's near value.
 * @param {Node<float>} far - The camera's far value.
 * @returns {Node<float>}
 */
const perspectiveDepthToViewZ = /*@__PURE__*/ Fn( ( [ depth, near, far ], builder ) => {

	if ( builder.renderer.reversedDepthBuffer === true ) {

		return near.mul( far ).div( near.sub( far ).mul( depth ).sub( near ) );

	} else {

		return near.mul( far ).div( far.sub( near ).mul( depth ).sub( far ) );

	}

} );

/**
 * TSL function for converting a viewZ value to a logarithmic depth value.
 *
 * @tsl
 * @function
 * @param {Node<float>} viewZ - The viewZ node.
 * @param {Node<float>} near - The camera's near value.
 * @param {Node<float>} far - The camera's far value.
 * @returns {Node<float>}
 */
const viewZToLogarithmicDepth = ( viewZ, near, far ) => {

	// NOTE: viewZ must be negative--see explanation at the end of this comment block.
	// The final logarithmic depth formula used here is adapted from one described in an
	// article by Thatcher Ulrich (see http://tulrich.com/geekstuff/log_depth_buffer.txt),
	// which was an improvement upon an earlier formula one described in an
	// Outerra article (https://outerra.blogspot.com/2009/08/logarithmic-z-buffer.html).
	// Ulrich's formula is the following:
	//     z = K * log( w / cameraNear ) / log( cameraFar / cameraNear )
	//     where K = 2^k - 1, and k is the number of bits in the depth buffer.
	// The Outerra variant ignored the camera near plane (it assumed it was 0) and instead
	// opted for a "C-constant" for resolution adjustment of objects near the camera.
	// Outerra states: "Notice that the 'C' variant doesn’t use a near plane distance, it has it
	// set at 0" (quote from https://outerra.blogspot.com/2012/11/maximizing-depth-buffer-range-and.html).
	// Ulrich's variant has the benefit of constant relative precision over the whole near-far range.
	// It was debated here whether Outerra's "C-constant" or Ulrich's "near plane" variant should
	// be used, and ultimately Ulrich's "near plane" version was chosen.
	// Outerra eventually made another improvement to their original "C-constant" variant,
	// but it still does not incorporate the camera near plane (for this version,
	// see https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html).
	// Here we make 4 changes to Ulrich's formula:
	// 1. Clamp the camera near plane so we don't divide by 0.
	// 2. Use log2 instead of log to avoid an extra multiply (shaders implement log using log2).
	// 3. Assume K is 1 (K = maximum value in depth buffer; see Ulrich's formula above).
	// 4. To maintain consistency with the functions "viewZToOrthographicDepth" and "viewZToPerspectiveDepth",
	//    we modify the formula here to use 'viewZ' instead of 'w'. The other functions expect a negative viewZ,
	//    so we do the same here, hence the 'viewZ.negate()' call.
	// For visual representation of this depth curve, see https://www.desmos.com/calculator/uyqk0vex1u
	near = near.max( EPSILON ).toVar();
	const numerator = log2( viewZ.negate().div( near ) );
	const denominator = log2( far.div( near ) );
	return numerator.div( denominator );

};

/**
 * TSL function for converting a logarithmic depth value to a viewZ value.
 *
 * @tsl
 * @function
 * @param {Node<float>} depth - The logarithmic depth.
 * @param {Node<float>} near - The camera's near value.
 * @param {Node<float>} far - The camera's far value.
 * @returns {Node<float>}
 */
const logarithmicDepthToViewZ = ( depth, near, far ) => {

	// NOTE: we add a 'negate()' call to the return value here to maintain consistency with
	// the functions "orthographicDepthToViewZ" and "perspectiveDepthToViewZ" (they return
	// a negative viewZ).
	const exponent = depth.mul( log( far.div( near ) ) );
	return float( Math.E ).pow( exponent ).mul( near ).negate();

};

/**
 * TSL function for retrieving the depth value as produced by the camera's projection matrix.
 *
 * @tsl
 * @function
 * @param {Node<float>} depth - The depth value.
 * @param {Node<float>} near - The camera's near value.
 * @param {Node<float>} far - The camera's far value.
 * @returns {Node<float>}
 */
const projectionDepth = /*@__PURE__*/ Fn( ( [ depth, near, far ], builder ) => {

	if ( builder.renderer.logarithmicDepthBuffer === true ) {

		const viewZ = logarithmicDepthToViewZ( depth, near, far );

		return viewZToPerspectiveDepth( viewZ, near, far );

	}

	return depth;

} );

/**
 * TSL function for defining a value for the current fragment's depth.
 *
 * @tsl
 * @function
 * @param {Node<float>} value - The depth value to set.
 * @returns {ViewportDepthNode<float>}
 */
const depthBase = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE );

/**
 * TSL object that represents the depth value for the current fragment.
 *
 * @tsl
 * @type {ViewportDepthNode}
 */
const depth = /*@__PURE__*/ nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH );

/**
 * TSL function for converting a perspective depth value to linear depth.
 *
 * @tsl
 * @function
 * @param {?Node<float>} [value=null] - The perspective depth. If `null` is provided, the current fragment's depth is used.
 * @returns {ViewportDepthNode<float>}
 */
const linearDepth = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.LINEAR_DEPTH ).setParameterLength( 0, 1 );

/**
 * TSL object that represents the linear (orthographic) depth value of the current fragment
 *
 * @tsl
 * @type {ViewportDepthNode}
 */
const viewportLinearDepth = /*@__PURE__*/ linearDepth( viewportDepthTexture() );

depth.assign = ( value ) => depthBase( value );

/**
 * A special version of a screen uv function that involves a depth comparison
 * when computing the final uvs. The function mitigates visual errors when
 * using viewport texture nodes for refraction purposes. Without this function
 * objects in front of a refractive surface might appear on the refractive surface
 * which is incorrect.
 *
 * @tsl
 * @function
 * @param {?Node<vec2>} uv - Optional uv coordinates. By default `screenUV` is used.
 * @return {Node<vec2>} The update uv coordinates.
 */
const viewportSafeUV = /*@__PURE__*/ Fn( ( [ uv = null ] ) => {

	const depth = linearDepth();
	const depthDiff = linearDepth( viewportDepthTexture( uv ) ).sub( depth );
	const finalUV = depthDiff.lessThan( 0 ).select( screenUV, uv );

	return finalUV;

} );

/**
 * TSL function for computing texture coordinates for animated sprite sheets.
 *
 * ```js
 * const uvNode = spritesheetUV( vec2( 6, 6 ), uv(), time.mul( animationSpeed ) );
 *
 * material.colorNode = texture( spriteSheet, uvNode );
 * ```
 *
 * @tsl
 * @function
 * @param {Node<vec2>} countNode - The node that defines the number of sprites in the x and y direction (e.g 6x6).
 * @param {?Node<vec2>} [uvNode=uv()] - The uv node.
 * @param {?Node<float>} [frameNode=float(0)] - The node that defines the current frame/sprite.
 * @returns {Node<vec2>}
 */
const spritesheetUV = /*@__PURE__*/ Fn( ( [ countNode, uvNode = uv$1(), frameNode = float( 0 ) ] ) => {

	const width = countNode.x;
	const height = countNode.y;

	const frameNum = frameNode.mod( width.mul( height ) ).floor();

	const column = frameNum.mod( width );
	const row = height.sub( frameNum.add( 1 ).div( width ).ceil() );

	const scale = countNode.reciprocal();
	const uvFrameOffset = vec2( column, row );

	return uvNode.add( uvFrameOffset ).mul( scale );

} );

/**
 * TSL function for creating a triplanar textures node.
 *
 * Can be used for triplanar texture mapping.
 *
 * ```js
 * material.colorNode = triplanarTexture( texture( diffuseMap ) );
 * ```
 *
 * @tsl
 * @function
 * @param {Node} textureXNode - First texture node.
 * @param {?Node} [textureYNode=null] - Second texture node. When not set, the shader will sample from `textureXNode` instead.
 * @param {?Node} [textureZNode=null] - Third texture node. When not set, the shader will sample from `textureXNode` instead.
 * @param {?Node<float>} [scaleNode=float(1)] - The scale node.
 * @param {?Node<vec3>} [positionNode=positionLocal] - Vertex positions in local space.
 * @param {?Node<vec3>} [normalNode=normalLocal] - Normals in local space.
 * @returns {Node<vec4>}
 */
const triplanarTextures = /*@__PURE__*/ Fn( ( [ textureXNode, textureYNode = null, textureZNode = null, scaleNode = float( 1 ), positionNode = positionLocal, normalNode = normalLocal ] ) => {

	// Reference: https://github.com/keijiro/StandardTriplanar

	// Blending factor of triplanar mapping
	let bf = normalNode.abs().normalize();
	bf = bf.div( bf.dot( vec3( 1.0 ) ) );

	// Triplanar mapping
	const tx = positionNode.yz.mul( scaleNode );
	const ty = positionNode.zx.mul( scaleNode );
	const tz = positionNode.xy.mul( scaleNode );

	// Base color
	const textureX = textureXNode.value;
	const textureY = textureYNode !== null ? textureYNode.value : textureX;
	const textureZ = textureZNode !== null ? textureZNode.value : textureX;

	const cx = texture( textureX, tx ).mul( bf.x );
	const cy = texture( textureY, ty ).mul( bf.y );
	const cz = texture( textureZ, tz ).mul( bf.z );

	return add( cx, cy, cz );

} );

/**
 * TSL function for creating a triplanar textures node.
 *
 * @tsl
 * @function
 * @param {Node} textureXNode - First texture node.
 * @param {?Node} [textureYNode=null] - Second texture node. When not set, the shader will sample from `textureXNode` instead.
 * @param {?Node} [textureZNode=null] - Third texture node. When not set, the shader will sample from `textureXNode` instead.
 * @param {?Node<float>} [scaleNode=float(1)] - The scale node.
 * @param {?Node<vec3>} [positionNode=positionLocal] - Vertex positions in local space.
 * @param {?Node<vec3>} [normalNode=normalLocal] - Normals in local space.
 * @returns {Node<vec4>}
 */
const triplanarTexture = ( ...params ) => triplanarTextures( ...params );

const _reflectorPlane = new Plane();
const _normal = new Vector3();
const _reflectorWorldPosition = new Vector3();
const _cameraWorldPosition = new Vector3();
const _rotationMatrix = new Matrix4();
const _lookAtPosition = new Vector3( 0, 0, -1 );
const clipPlane = new Vector4();

const _view = new Vector3();
const _target = new Vector3();
const _q = new Vector4();

const _size$2 = new Vector2();

const _defaultRT = new RenderTarget();
const _defaultUV = screenUV.flipX();

_defaultRT.depthTexture = new DepthTexture( 1, 1 );

let _inReflector = false;

/**
 * This node can be used to implement mirror-like flat reflective surfaces.
 *
 * ```js
 * const groundReflector = reflector();
 * material.colorNode = groundReflector;
 *
 * const plane = new Mesh( geometry, material );
 * plane.add( groundReflector.target );
 * ```
 *
 * @augments TextureNode
 */
class ReflectorNode extends TextureNode {

	static get type() {

		return 'ReflectorNode';

	}

	/**
	 * Constructs a new reflector node.
	 *
	 * @param {Object} [parameters={}] - An object holding configuration parameters.
	 * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to.
	 * @param {number} [parameters.resolutionScale=1] - The resolution scale.
	 * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not.
	 * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not.
	 * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not.
	 * @param {number} [parameters.samples] - Anti-Aliasing samples of the internal render-target.
	 * @param {TextureNode} [parameters.defaultTexture] - The default texture node.
	 * @param {ReflectorBaseNode} [parameters.reflector] - The reflector base node.
	 */
	constructor( parameters = {} ) {

		super( parameters.defaultTexture || _defaultRT.texture, _defaultUV );

		/**
		 * A reference to the internal reflector base node which holds the actual implementation.
		 *
		 * @private
		 * @type {ReflectorBaseNode}
		 * @default ReflectorBaseNode
		 */
		this._reflectorBaseNode = parameters.reflector || new ReflectorBaseNode( this, parameters );

		/**
		 * A reference to the internal depth node.
		 *
		 * @private
		 * @type {?Node}
		 * @default null
		 */
		this._depthNode = null;

		this.setUpdateMatrix( false );

	}

	/**
	 * A reference to the internal reflector node.
	 *
	 * @type {ReflectorBaseNode}
	 */
	get reflector() {

		return this._reflectorBaseNode;

	}

	/**
	 * A reference to 3D object the reflector is linked to.
	 *
	 * @type {Object3D}
	 */
	get target() {

		return this._reflectorBaseNode.target;

	}

	/**
	 * Returns a node representing the mirror's depth. That can be used
	 * to implement more advanced reflection effects like distance attenuation.
	 *
	 * @return {Node} The depth node.
	 */
	getDepthNode() {

		if ( this._depthNode === null ) {

			if ( this._reflectorBaseNode.depth !== true ) {

				throw new Error( 'THREE.ReflectorNode: Depth node can only be requested when the reflector is created with { depth: true }. ' );

			}

			this._depthNode = new ReflectorNode( {
				defaultTexture: _defaultRT.depthTexture,
				reflector: this._reflectorBaseNode
			} );

		}

		return this._depthNode;

	}

	setup( builder ) {

		// ignore if used in post-processing
		if ( ! builder.object.isQuadMesh ) this._reflectorBaseNode.build( builder );

		return super.setup( builder );

	}

	clone() {

		const newNode = new this.constructor( this.reflectorNode );
		newNode.uvNode = this.uvNode;
		newNode.levelNode = this.levelNode;
		newNode.biasNode = this.biasNode;
		newNode.sampler = this.sampler;
		newNode.depthNode = this.depthNode;
		newNode.compareNode = this.compareNode;
		newNode.gradNode = this.gradNode;
		newNode.gatherNode = this.gatherNode;
		newNode.offsetNode = this.offsetNode;
		newNode._reflectorBaseNode = this._reflectorBaseNode;

		return newNode;

	}

	/**
	 * Frees internal resources. Should be called when the node is no longer in use.
	 */
	dispose() {

		super.dispose();

		this._reflectorBaseNode.dispose();

	}

}

/**
 * Holds the actual implementation of the reflector (virtual cameras, render
 * targets and the reflection rendering).
 *
 * This logic is kept separate from {@link ReflectorNode} because the latter is
 * a {@link TextureNode} representing a single texture view. A reflector can be
 * sampled by multiple texture nodes at once - e.g. one for color and one for
 * depth (see {@link ReflectorNode#getDepthNode}) - which all reference the same
 * base node so the reflection is rendered only once.
 *
 * @private
 * @augments Node
 */
class ReflectorBaseNode extends Node {

	static get type() {

		return 'ReflectorBaseNode';

	}

	/**
	 * Constructs a new reflector base node.
	 *
	 * @param {TextureNode} textureNode - Represents the rendered reflections as a texture node.
	 * @param {Object} [parameters={}] - An object holding configuration parameters.
	 * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to.
	 * @param {number} [parameters.resolutionScale=1] - The resolution scale.
	 * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not.
	 * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not.
	 * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not.
	 * @param {number} [parameters.samples] - Anti-Aliasing samples of the internal render-target.
	 */
	constructor( textureNode, parameters = {} ) {

		super();

		const {
			target = new Object3D(),
			resolutionScale = 1,
			generateMipmaps = false,
			bounces = true,
			depth = false,
			samples = 0
		} = parameters;

		/**
		 * Represents the rendered reflections as a texture node.
		 *
		 * @type {TextureNode}
		 */
		this.textureNode = textureNode;

		/**
		 * The 3D object the reflector is linked to.
		 *
		 * @type {Object3D}
		 * @default {new Object3D()}
		 */
		this.target = target;

		/**
		 * The resolution scale.
		 *
		 * @type {number}
		 * @default {1}
		 */
		this.resolutionScale = resolutionScale;

		if ( parameters.resolution !== undefined ) {

			warnOnce( 'ReflectorNode: The "resolution" parameter has been renamed to "resolutionScale".' ); // @deprecated r180

			this.resolutionScale = parameters.resolution;

		}

		/**
		 * Whether mipmaps should be generated or not.
		 *
		 * @type {boolean}
		 * @default {false}
		 */
		this.generateMipmaps = generateMipmaps;

		/**
		 * Whether reflectors can render other reflector nodes or not.
		 *
		 * @type {boolean}
		 * @default {true}
		 */
		this.bounces = bounces;

		/**
		 * Whether depth data should be generated or not.
		 *
		 * @type {boolean}
		 * @default {false}
		 */
		this.depth = depth;

		/**
		 * The number of anti-aliasing samples for the render-target
		 *
		 * @type {number}
		 * @default {0}
		 */
		this.samples = samples;

		/**
		 * The `updateBeforeType` is set to `NodeUpdateType.RENDER` when {@link ReflectorBaseNode#bounces}
		 * is `true`. Otherwise it's `NodeUpdateType.FRAME`.
		 *
		 * @type {string}
		 * @default 'render'
		 */
		this.updateBeforeType = bounces ? NodeUpdateType.RENDER : NodeUpdateType.FRAME;

		/**
		 * Weak map for managing virtual cameras.
		 *
		 * @type {WeakMap<Camera, Camera>}
		 */
		this.virtualCameras = new WeakMap();

		/**
		 * Weak map for managing render targets.
		 *
		 * @type {Map<Camera, RenderTarget>}
		 */
		this.renderTargets = new Map();

		/**
		 * Force render even if reflector is facing away from camera.
		 *
		 * @type {boolean}
		 * @default {false}
		 */
		this.forceUpdate = false;

		/**
		 * Whether the reflector has been rendered or not.
		 *
		 * When the reflector is facing away from the camera,
		 * this flag is set to `false` and the texture will be empty(black).
		 *
		 * @type {boolean}
		 * @default {false}
		 */
		this.hasOutput = false;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Updates the resolution of the internal render target.
	 *
	 * @private
	 * @param {RenderTarget} renderTarget - The render target to resize.
	 * @param {Renderer} renderer - The renderer that is used to determine the new size.
	 */
	_updateResolution( renderTarget, renderer ) {

		const resolution = this.resolutionScale;

		renderer.getDrawingBufferSize( _size$2 );

		renderTarget.setSize( Math.round( _size$2.width * resolution ), Math.round( _size$2.height * resolution ) );

	}

	setup( builder ) {

		this._updateResolution( _defaultRT, builder.renderer );

		return super.setup( builder );

	}

	/**
	 * Frees internal resources. Should be called when the node is no longer in use.
	 */
	dispose() {

		super.dispose();

		for ( const renderTarget of this.renderTargets.values() ) {

			renderTarget.dispose();

		}

	}

	/**
	 * Returns a virtual camera for the given camera. The virtual camera is used to
	 * render the scene from the reflector's view so correct reflections can be produced.
	 *
	 * @param {Camera} camera - The scene's camera.
	 * @return {Camera} The corresponding virtual camera.
	 */
	getVirtualCamera( camera ) {

		let virtualCamera = this.virtualCameras.get( camera );

		if ( virtualCamera === undefined ) {

			virtualCamera = camera.clone();

			this.virtualCameras.set( camera, virtualCamera );

		}

		return virtualCamera;

	}

	/**
	 * Returns a render target for the given camera. The reflections are rendered
	 * into this render target.
	 *
	 * @param {Camera} camera - The scene's camera.
	 * @return {RenderTarget} The render target.
	 */
	getRenderTarget( camera ) {

		let renderTarget = this.renderTargets.get( camera );

		if ( renderTarget === undefined ) {

			renderTarget = new RenderTarget( 1, 1, { type: HalfFloatType, samples: this.samples } );

			if ( this.generateMipmaps === true ) {

				renderTarget.texture.minFilter = LinearMipMapLinearFilter;
				renderTarget.texture.generateMipmaps = true;

			}

			if ( this.depth === true ) {

				renderTarget.depthTexture = new DepthTexture();

			}

			this.renderTargets.set( camera, renderTarget );

		}

		return renderTarget;

	}

	/**
	 * Uses the virtual camera of the current camera as the update reference so
	 * the reflection is updated for each camera that renders the reflector.
	 *
	 * @param {(NodeFrame|NodeBuilder)} state - The current state.
	 * @return {Camera} The virtual camera.
	 */
	updateReference( state ) {

		return this.getVirtualCamera( state.camera );

	}

	updateBefore( frame ) {

		if ( this.bounces === false && _inReflector ) return false;

		_inReflector = true;

		const { scene, camera, renderer, material } = frame;
		const { target } = this;

		const virtualCamera = this.getVirtualCamera( camera );
		const renderTarget = this.getRenderTarget( virtualCamera );

		renderer.getDrawingBufferSize( _size$2 );

		this._updateResolution( renderTarget, renderer );

		//

		_reflectorWorldPosition.setFromMatrixPosition( target.matrixWorld );
		_cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );

		_rotationMatrix.extractRotation( target.matrixWorld );

		_normal.set( 0, 0, 1 );
		_normal.applyMatrix4( _rotationMatrix );

		_view.subVectors( _reflectorWorldPosition, _cameraWorldPosition );

		// Avoid rendering when reflector is facing away unless forcing an update
		const isFacingAway = _view.dot( _normal ) > 0;

		let needsClear = false;

		if ( isFacingAway === true && this.forceUpdate === false ) {

			if ( this.hasOutput === false ) {

				_inReflector = false;

				return;

			}

			needsClear = true;

		}

		_view.reflect( _normal ).negate();
		_view.add( _reflectorWorldPosition );

		_rotationMatrix.extractRotation( camera.matrixWorld );

		_lookAtPosition.set( 0, 0, -1 );
		_lookAtPosition.applyMatrix4( _rotationMatrix );
		_lookAtPosition.add( _cameraWorldPosition );

		_target.subVectors( _reflectorWorldPosition, _lookAtPosition );
		_target.reflect( _normal ).negate();
		_target.add( _reflectorWorldPosition );

		//

		virtualCamera.coordinateSystem = camera.coordinateSystem;
		virtualCamera.position.copy( _view );
		virtualCamera.up.set( 0, 1, 0 );
		virtualCamera.up.applyMatrix4( _rotationMatrix );
		virtualCamera.up.reflect( _normal );
		virtualCamera.lookAt( _target );

		virtualCamera.near = camera.near;
		virtualCamera.far = camera.far;

		virtualCamera.updateMatrixWorld();
		virtualCamera.projectionMatrix.copy( camera.projectionMatrix );

		// The reflection is mirrored horizontally, so the view offset must be mirrored as well.

		virtualCamera.projectionMatrix.elements[ 8 ] *= -1;
		virtualCamera.projectionMatrix.elements[ 12 ] *= -1;

		// Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
		// Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
		_reflectorPlane.setFromNormalAndCoplanarPoint( _normal, _reflectorWorldPosition );
		_reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );

		clipPlane.set( _reflectorPlane.normal.x, _reflectorPlane.normal.y, _reflectorPlane.normal.z, _reflectorPlane.constant );

		const projectionMatrix = virtualCamera.projectionMatrix;

		_q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
		_q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];
		_q.z = -1;
		_q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];

		// Calculate the scaled plane vector
		clipPlane.multiplyScalar( 1.0 / clipPlane.dot( _q ) );

		const clipBias = 0;

		// Replacing the third row of the projection matrix
		projectionMatrix.elements[ 2 ] = clipPlane.x;
		projectionMatrix.elements[ 6 ] = clipPlane.y;
		projectionMatrix.elements[ 10 ] = ( renderer.coordinateSystem === WebGPUCoordinateSystem ) ? ( clipPlane.z - clipBias ) : ( clipPlane.z + 1.0 - clipBias );
		projectionMatrix.elements[ 14 ] = clipPlane.w;

		//

		this.textureNode.value = renderTarget.texture;

		if ( this.depth === true ) {

			this.textureNode.getDepthNode().value = renderTarget.depthTexture;

		}

		material.visible = false;

		const currentRenderTarget = renderer.getRenderTarget();
		const currentMRT = renderer.getMRT();
		const currentAutoClear = renderer.autoClear;

		renderer.setMRT( null );
		renderer.setRenderTarget( renderTarget );
		renderer.autoClear = true;

		const previousName = scene.name;

		scene.name = ( scene.name || 'Scene' ) + ' [ Reflector ]'; // TODO: Add bounce index

		if ( needsClear ) {

			renderer.clear();

			this.hasOutput = false;

		} else {

			renderer.render( scene, virtualCamera );

			this.hasOutput = true;

		}

		scene.name = previousName;

		renderer.setMRT( currentMRT );
		renderer.setRenderTarget( currentRenderTarget );
		renderer.autoClear = currentAutoClear;

		material.visible = true;

		_inReflector = false;

		this.forceUpdate = false;

	}

	/**
	 * The resolution scale.
	 *
	 * @deprecated
	 * @type {number}
	 * @default {1}
	 */
	get resolution() {

		warnOnce( 'ReflectorNode: The "resolution" property has been renamed to "resolutionScale".' ); // @deprecated r180

		return this.resolutionScale;

	}

	set resolution( value ) {

		warnOnce( 'ReflectorNode: The "resolution" property has been renamed to "resolutionScale".' ); // @deprecated r180

		this.resolutionScale = value;

	}

}

/**
 * TSL function for creating a reflector node.
 *
 * @tsl
 * @function
 * @param {Object} [parameters={}] - An object holding configuration parameters.
 * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to.
 * @param {number} [parameters.resolution=1] - The resolution scale.
 * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not.
 * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not.
 * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not.
 * @param {number} [parameters.samples] - Anti-Aliasing samples of the internal render-target.
 * @param {TextureNode} [parameters.defaultTexture] - The default texture node.
 * @param {ReflectorBaseNode} [parameters.reflector] - The reflector base node.
 * @returns {ReflectorNode}
 */
const reflector = ( parameters ) => new ReflectorNode( parameters );

const _m1$1 = /*@__PURE__*/ new Matrix4();

/**
 * TSL object that represents the refraction ratio of the material used for rendering the current object.
 *
 * @tsl
 * @type {UniformNode<float>}
 */
const materialRefractionRatio = /*@__PURE__*/ uniform( 0 ).onReference( ( { material } ) => material ).onObjectUpdate( ( { material } ) => material.refractionRatio );

/**
 * TSL object that represents the intensity of environment maps of PBR materials.
 * When `material.envMap` is set, the value is `material.envMapIntensity` otherwise `scene.environmentIntensity`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialEnvIntensity = /*@__PURE__*/ uniform( 1 ).onReference( ( { material } ) => material ).onObjectUpdate( function ( { material, scene } ) {

	return material.envMap ? material.envMapIntensity : scene.environmentIntensity;

} );

/**
 * TSL object that represents the rotation of environment maps.
 * When `material.envMap` is set, the value is `material.envMapRotation`.
 * `scene.environmentRotation` controls the rotation of `scene.environment` or `scene.environmentNode` instead.
 *
 * @tsl
 * @type {Node<mat4>}
 */
const materialEnvRotation = /*@__PURE__*/ uniform( new Matrix4() ).onReference( function ( frame ) {

	return frame.material;

} ).onObjectUpdate( function ( { material, scene } ) {

	const hasSceneEnvironment = ( scene.environment !== null ) || ( scene.environmentNode && scene.environmentNode.isNode );
	const rotation = ( hasSceneEnvironment && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation;

	if ( rotation ) {

		// note: since the matrix is orthonormal, we can use the more-efficient transpose() in lieu of invert()
		_m1$1.makeRotationFromEuler( rotation ).transpose();

	} else {

		_m1$1.identity();

	}

	return _m1$1;

} );

/**
 * The reflect vector in view space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const reflectView = /*@__PURE__*/ positionViewDirection.negate().reflect( normalView );

/**
 * The refract vector in view space.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const refractView = /*@__PURE__*/ positionViewDirection.negate().refract( normalView, materialRefractionRatio );

/**
 * Used for sampling cube maps when using cube reflection mapping.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const reflectVector = /*@__PURE__*/ reflectView.transformDirection( cameraWorldMatrix ).toVar( 'reflectVector' );

/**
 * Used for sampling cube maps when using cube refraction mapping.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const refractVector = /*@__PURE__*/ refractView.transformDirection( cameraWorldMatrix ).toVar( 'refractVector' );

const EmptyTexture = /*@__PURE__*/ new CubeTexture();

/**
 * This type of uniform node represents a cube texture.
 *
 * @augments TextureNode
 */
class CubeTextureNode extends TextureNode {

	static get type() {

		return 'CubeTextureNode';

	}

	/**
	 * Constructs a new cube texture node.
	 *
	 * @param {CubeTexture} value - The cube texture.
	 * @param {?Node<vec3>} [uvNode=null] - The uv node.
	 * @param {?Node<int>} [levelNode=null] - The level node.
	 * @param {?Node<float>} [biasNode=null] - The bias node.
	 */
	constructor( value, uvNode = null, levelNode = null, biasNode = null ) {

		super( value, uvNode, levelNode, biasNode );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isCubeTextureNode = true;

	}

	/**
	 * Overwrites the default implementation to return the appropriate cube texture type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( /*builder*/ ) {

		if ( this.value.isDepthTexture === true ) {

			return 'cubeDepthTexture';

		}

		return 'cubeTexture';

	}

	/**
	 * Returns a default uvs based on the mapping type of the cube texture.
	 *
	 * @return {Node<vec3>} The default uv attribute.
	 */
	getDefaultUV() {

		const texture = this.value;

		if ( texture.mapping === CubeReflectionMapping ) {

			return reflectVector;

		} else if ( texture.mapping === CubeRefractionMapping ) {

			return refractVector;

		} else {

			error( 'CubeTextureNode: Mapping "%s" not supported.', texture.mapping );

			return vec3( 0, 0, 0 );

		}

	}

	/**
	 * Overwritten with an empty implementation since the `updateMatrix` flag is ignored
	 * for cube textures. The uv transformation matrix is not applied to cube textures.
	 *
	 * @param {boolean} value - The update toggle.
	 */
	setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for CubeTextureNode

	/**
	 * Setups the uv node. Depending on the backend as well as the texture type, it might be necessary
	 * to modify the uv node for correct sampling.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node} uvNode - The uv node to setup.
	 * @return {Node} The updated uv node.
	 */
	setupUV( builder, uvNode ) {

		const texture = this.value;

		if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem || ( ! texture.isRenderTargetTexture && ! texture.isDepthTexture ) ) {

			uvNode = vec3( uvNode.x.negate(), uvNode.yz );

		}

		return uvNode;

	}

	/**
	 * Generates the uv code snippet.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node} cubeUV - The uv node to generate code for.
	 * @return {string} The generated code snippet.
	 */
	generateUV( builder, cubeUV ) {

		return cubeUV.build( builder, this.sampler === true ? 'vec3' : 'ivec3' );

	}

}

/**
 * TSL function for creating a cube texture node.
 *
 * @tsl
 * @function
 * @param {CubeTexture} value - The cube texture.
 * @param {?Node<vec3>} [uvNode=null] - The uv node.
 * @param {?Node<int>} [levelNode=null] - The level node.
 * @param {?Node<float>} [biasNode=null] - The bias node.
 * @returns {CubeTextureNode}
 */
const cubeTextureBase = /*@__PURE__*/ nodeProxy( CubeTextureNode ).setParameterLength( 1, 4 ).setName( 'cubeTexture' );

/**
 * TSL function for creating a cube texture uniform node.
 *
 * @tsl
 * @function
 * @param {?(CubeTexture|CubeTextureNode)} [value=EmptyTexture] - The cube texture.
 * @param {?Node<vec3>} [uvNode=null] - The uv node.
 * @param {?Node<int>} [levelNode=null] - The level node.
 * @param {?Node<float>} [biasNode=null] - The bias node.
 * @returns {CubeTextureNode}
 */
const cubeTexture = ( value = EmptyTexture, uvNode = null, levelNode = null, biasNode = null ) => {

	let textureNode;

	if ( value && value.isCubeTextureNode === true ) {

		textureNode = value.clone();
		textureNode.referenceNode = value; // Ensure the reference is set to the original node

		if ( uvNode !== null ) textureNode.uvNode = nodeObject( uvNode );
		if ( levelNode !== null ) textureNode.levelNode = nodeObject( levelNode );
		if ( biasNode !== null ) textureNode.biasNode = nodeObject( biasNode );

	} else {

		textureNode = cubeTextureBase( value, uvNode, levelNode, biasNode );

	}

	return textureNode;

};

/**
 * TSL function for creating a non-mergeable uniform cube texture node.
 *
 * @tsl
 * @function
 * @param {?CubeTexture} [value=EmptyTexture] - The cube texture.
 * @returns {CubeTextureNode}
 */
const uniformCubeTexture = ( value = EmptyTexture ) => {

	const textureNode = cubeTextureBase( value );

	textureNode.mergeable = false;

	return textureNode;

};

// TODO: Avoid duplicated code and use only ReferenceBaseNode or ReferenceNode

/**
 * This type of node establishes a reference to a property of another object.
 * In this way, the value of the node is automatically linked to the value of
 * referenced object. Reference nodes internally represent the linked value
 * as a uniform.
 *
 * @augments Node
 */
class ReferenceNode extends Node {

	static get type() {

		return 'ReferenceNode';

	}

	/**
	 * Constructs a new reference node.
	 *
	 * @param {string} property - The name of the property the node refers to.
	 * @param {string} uniformType - The uniform type that should be used to represent the property value.
	 * @param {?Object} [object=null] - The object the property belongs to.
	 * @param {?number} [count=null] - When the linked property is an array-like, this parameter defines its length.
	 */
	constructor( property, uniformType, object = null, count = null ) {

		super();

		/**
		 * The name of the property the node refers to.
		 *
		 * @type {string}
		 */
		this.property = property;

		/**
		 * The uniform type that should be used to represent the property value.
		 *
		 * @type {string}
		 */
		this.uniformType = uniformType;

		/**
		 * The object the property belongs to.
		 *
		 * @type {?Object}
		 * @default null
		 */
		this.object = object;

		/**
		 * When the linked property is an array, this parameter defines its length.
		 *
		 * @type {?number}
		 * @default null
		 */
		this.count = count;

		/**
		 * The property name might have dots so nested properties can be referred.
		 * The hierarchy of the names is stored inside this array.
		 *
		 * @type {Array<string>}
		 */
		this.properties = property.split( '.' );

		/**
		 * Points to the current referred object. This property exists next to {@link ReferenceNode#object}
		 * since the final reference might be updated from calling code.
		 *
		 * @type {?Object}
		 * @default null
		 */
		this.reference = object;

		/**
		 * The uniform node that holds the value of the reference node.
		 *
		 * @type {UniformNode}
		 * @default null
		 */
		this.node = null;

		/**
		 * The uniform group of the internal uniform.
		 *
		 * @type {UniformGroupNode}
		 * @default null
		 */
		this.group = null;

		/**
		 * An optional label of the internal uniform node.
		 *
		 * @type {?string}
		 * @default null
		 */
		this.name = null;

		/**
		 * Overwritten since reference nodes are updated per object.
		 *
		 * @type {string}
		 * @default 'object'
		 */
		this.updateType = NodeUpdateType.OBJECT;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * When the referred property is array-like, this method can be used
	 * to access elements via an index node.
	 *
	 * @param {IndexNode} indexNode - indexNode.
	 * @return {ReferenceElementNode} A reference to an element.
	 */
	element( indexNode ) {

		return new ReferenceElementNode( this, nodeObject( indexNode ) );

	}

	/**
	 * Sets the uniform group for this reference node.
	 *
	 * @param {UniformGroupNode} group - The uniform group to set.
	 * @return {ReferenceNode} A reference to this node.
	 */
	setGroup( group ) {

		this.group = group;

		return this;

	}

	/**
	 * Sets the name for the internal uniform.
	 *
	 * @param {string} name - The label to set.
	 * @return {ReferenceNode} A reference to this node.
	 */
	setName( name ) {

		this.name = name;

		return this;

	}

	/**
	 * Sets the label for the internal uniform.
	 *
	 * @deprecated
	 * @param {string} name - The label to set.
	 * @return {ReferenceNode} A reference to this node.
	 */
	label( name ) {

		warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.' ); // @deprecated r179

		return this.setName( name );

	}

	/**
	 * Sets the node type which automatically defines the internal
	 * uniform type.
	 *
	 * @param {string} uniformType - The type to set.
	 */
	setNodeType( uniformType ) {

		let node = null;

		if ( this.count !== null ) {

			node = buffer( null, uniformType, this.count );

		} else if ( Array.isArray( this.getValueFromReference() ) ) {

			node = uniformArray( null, uniformType );
			node.updateType = NodeUpdateType.OBJECT;

		} else if ( uniformType === 'texture' ) {

			node = texture( null );

		} else if ( uniformType === 'cubeTexture' ) {

			node = cubeTexture( null );

		} else {

			node = uniform( null, uniformType );

		}

		if ( this.group !== null ) {

			node.setGroup( this.group );

		}

		if ( this.name !== null ) node.setName( this.name );

		this.node = node;

	}

	/**
	 * This method is overwritten since the node type is inferred from
	 * the type of the reference node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		if ( this.node === null ) {

			this.updateReference( builder );
			this.updateValue();

		}

		return this.node.getNodeType( builder );

	}

	/**
	 * Returns the property value from the given referred object.
	 *
	 * @param {Object} [object=this.reference] - The object to retrieve the property value from.
	 * @return {any} The value.
	 */
	getValueFromReference( object = this.reference ) {

		const { properties } = this;

		let value = object[ properties[ 0 ] ];

		for ( let i = 1; i < properties.length; i ++ ) {

			value = value[ properties[ i ] ];

		}

		return value;

	}

	/**
	 * Allows to update the reference based on the given state. The state is only
	 * evaluated {@link ReferenceNode#object} is not set.
	 *
	 * @param {(NodeFrame|NodeBuilder)} state - The current state.
	 * @return {Object} The updated reference.
	 */
	updateReference( state ) {

		this.reference = this.object !== null ? this.object : state.object;

		return this.reference;

	}

	/**
	 * The output of the reference node is the internal uniform node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {UniformNode} The output node.
	 */
	setup( /* builder */ ) {

		this.updateValue();

		return this.node;

	}

	/**
	 * Overwritten to update the internal uniform value.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	update( /*frame*/ ) {

		this.updateValue();

	}

	/**
	 * Retrieves the value from the referred object property and uses it
	 * to updated the internal uniform.
	 */
	updateValue() {

		if ( this.node === null ) this.setNodeType( this.uniformType );

		const value = this.getValueFromReference();

		if ( Array.isArray( value ) ) {

			this.node.array = value;

		} else {

			this.node.value = value;

		}

	}

}

/**
 * TSL function for creating a reference node.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the property the node refers to.
 * @param {string} type - The uniform type that should be used to represent the property value.
 * @param {?Object} [object] - The object the property belongs to.
 * @returns {ReferenceNode}
 */
const reference = ( name, type, object ) => new ReferenceNode( name, type, object );

/**
 * TSL function for creating a reference node. Use this function if you want need a reference
 * to an array-like property that should be represented as a uniform buffer.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the property the node refers to.
 * @param {string} type - The uniform type that should be used to represent the property value.
 * @param {number} count - The number of value inside the array-like object.
 * @param {Object} object - An array-like object the property belongs to.
 * @returns {ReferenceNode}
 */
const referenceBuffer = ( name, type, count, object ) => new ReferenceNode( name, type, object, count );

/**
 * This node is a special type of reference node which is intended
 * for linking material properties with node values.
 * ```js
 * const opacityNode = materialReference( 'opacity', 'float', material );
 * ```
 * When changing `material.opacity`, the node value of `opacityNode` will
 * automatically be updated.
 *
 * @augments ReferenceNode
 */
class MaterialReferenceNode extends ReferenceNode {

	static get type() {

		return 'MaterialReferenceNode';

	}

	/**
	 * Constructs a new material reference node.
	 *
	 * @param {string} property - The name of the property the node refers to.
	 * @param {string} inputType - The uniform type that should be used to represent the property value.
	 * @param {?Material} [material=null] - The material the property belongs to. When no material is set,
	 * the node refers to the material of the current rendered object.
	 */
	constructor( property, inputType, material = null ) {

		super( property, inputType, material );

		/**
		 * The material the property belongs to. When no material is set,
		 * the node refers to the material of the current rendered object.
		 *
		 * @type {?Material}
		 * @default null
		 */
		this.material = material;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isMaterialReferenceNode = true;

	}

	/**
	 * Updates the reference based on the given state. The state is only evaluated
	 * {@link MaterialReferenceNode#material} is not set.
	 *
	 * @param {(NodeFrame|NodeBuilder)} state - The current state.
	 * @return {Object} The updated reference.
	 */
	updateReference( state ) {

		this.reference = this.material !== null ? this.material : state.material;

		return this.reference;

	}

}

/**
 * TSL function for creating a material reference node.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the property the node refers to.
 * @param {string} type - The uniform type that should be used to represent the property value.
 * @param {?Material} [material=null] - The material the property belongs to.
 * When no material is set, the node refers to the material of the current rendered object.
 * @returns {MaterialReferenceNode}
 */
const materialReference = ( name, type, material = null ) => new MaterialReferenceNode( name, type, material );

// Normal Mapping Without Precomputed Tangents
// http://www.thetenthplanet.de/archives/1180

const uv = uv$1();

const q0 = positionView.dFdx();
const q1 = positionView.dFdy();
const st0 = uv.dFdx();
const st1 = uv.dFdy();

const N = normalView;

const q1perp = q1.cross( N );
const q0perp = N.cross( q0 );

const T = q1perp.mul( st0.x ).add( q0perp.mul( st1.x ) );
const B = q1perp.mul( st0.y ).add( q0perp.mul( st1.y ) );

const det = T.dot( T ).max( B.dot( B ) );
const scale = det.equal( 0.0 ).select( 0.0, det.inverseSqrt() );

/**
 * Tangent vector in view space, computed dynamically from geometry and UV derivatives.
 * Useful for normal mapping without precomputed tangents.
 *
 * Reference: http://www.thetenthplanet.de/archives/1180
 *
 * @tsl
 * @type {Node<vec3>}
 */
const tangentViewFrame = /*@__PURE__*/ T.mul( scale ).toVar( 'tangentViewFrame' );

/**
 * Bitangent vector in view space, computed dynamically from geometry and UV derivatives.
 * Complements the tangentViewFrame for constructing the tangent space basis.
 *
 * Reference: http://www.thetenthplanet.de/archives/1180
 *
 * @tsl
 * @type {Node<vec3>}
 */
const bitangentViewFrame = /*@__PURE__*/ B.mul( scale ).toVar( 'bitangentViewFrame' );

/**
 * TSL object that represents the tangent attribute of the current rendered object.
 *
 * @tsl
 * @type {Node<vec4>}
 */
const tangentGeometry = /*@__PURE__*/ attribute( 'tangent', 'vec4' );

/**
 * TSL object that represents the vertex tangent in local space of the current rendered object.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const tangentLocal = /*@__PURE__*/ tangentGeometry.xyz.toVar( 'tangentLocal' );

/**
 * TSL object that represents the vertex tangent in view space of the current rendered object.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const tangentView = /*@__PURE__*/ ( Fn( ( builder ) => {

	let node;

	if ( builder.subBuildFn === 'VERTEX' || builder.geometry.hasAttribute( 'tangent' ) ) {

		node = modelViewMatrix.mul( vec4( tangentLocal, 0 ) ).xyz.toVarying( 'v_tangentView' ).normalize();

	} else {

		node = tangentViewFrame;

	}

	if ( builder.isFlatShading() !== true ) {

		node = negateOnBackSide( node );

	}

	return node;

}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'tangentView' );

/**
 * TSL object that represents the vertex tangent in world space of the current rendered object.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const tangentWorld = /*@__PURE__*/ tangentView.transformDirection( cameraWorldMatrix ).toVarying( 'v_tangentWorld' ).normalize().toVar( 'tangentWorld' );

/**
 * Returns the bitangent node and assigns it to a varying if the material is not flat shaded.
 *
 * @tsl
 * @private
 * @param {Node<vec3>} crossNormalTangent - The cross product of the normal and tangent vectors.
 * @param {string} varyingName - The name of the varying to assign the bitangent to.
 * @returns {Node<vec3>} The bitangent node.
 */
const getBitangent = /*@__PURE__*/ Fn( ( [ crossNormalTangent, varyingName ], builder ) => {

	let bitangent = crossNormalTangent.mul( tangentGeometry.w ).xyz;

	if ( builder.subBuildFn === 'NORMAL' && builder.isFlatShading() !== true ) {

		bitangent = bitangent.toVarying( varyingName );

	}

	return bitangent;

} ).once( [ 'NORMAL' ] );

/**
 * TSL object that represents the bitangent attribute of the current rendered object.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const bitangentGeometry = /*@__PURE__*/ getBitangent( normalGeometry.cross( tangentGeometry ), 'v_bitangentGeometry' ).normalize().toVar( 'bitangentGeometry' );

/**
 * TSL object that represents the vertex bitangent in local space of the current rendered object.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const bitangentLocal = /*@__PURE__*/ getBitangent( normalLocal.cross( tangentLocal ), 'v_bitangentLocal' ).normalize().toVar( 'bitangentLocal' );

/**
 * TSL object that represents the vertex bitangent in view space of the current rendered object.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const bitangentView = /*@__PURE__*/ ( Fn( ( builder ) => {

	let node;

	if ( builder.subBuildFn === 'VERTEX' || builder.geometry.hasAttribute( 'tangent' ) ) {

		node = getBitangent( normalView.cross( tangentView ), 'v_bitangentView' ).normalize();

	} else {

		node = bitangentViewFrame;

	}

	if ( builder.isFlatShading() !== true ) {

		node = negateOnBackSide( node );

	}

	return node;

}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'bitangentView' );

/**
 * TSL object that represents the vertex bitangent in world space of the current rendered object.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const bitangentWorld = /*@__PURE__*/ getBitangent( normalWorld.cross( tangentWorld ), 'v_bitangentWorld' ).normalize().toVar( 'bitangentWorld' );

/**
 * TSL object that represents the TBN matrix in view space.
 *
 * @tsl
 * @type {Node<mat3>}
 */
const TBNViewMatrix = /*@__PURE__*/ mat3( tangentView, bitangentView, normalView ).toVar( 'TBNViewMatrix' );

/**
 * TSL object that represents the parallax direction.
 *
 * @tsl
 * @type {Node<mat3>}
 */
const parallaxDirection = /*@__PURE__*/ positionViewDirection.mul( TBNViewMatrix )/*.normalize()*/;

/**
 * TSL function for computing parallax uv coordinates.
 *
 * @tsl
 * @function
 * @param {Node<vec2>} uv - A uv node.
 * @param {Node<vec2>} scale - A scale node.
 * @returns {Node<vec2>} Parallax uv coordinates.
 */
const parallaxUV = ( uv, scale ) => uv.sub( parallaxDirection.mul( scale ) );

/**
 * TSL function for computing bent normals.
 *
 * @tsl
 * @function
 * @returns {Node<vec3>} Bent normals.
 */
const bentNormalView = /*@__PURE__*/ ( Fn( () => {

	// https://google.github.io/filament/Filament.md.html#lighting/imagebasedlights/anisotropy

	let bentNormal = anisotropyB.cross( positionViewDirection );
	bentNormal = bentNormal.cross( anisotropyB ).normalize();
	bentNormal = mix( bentNormal, normalView, anisotropy.mul( roughness.oneMinus() ).oneMinus().pow2().pow2() ).normalize();

	return bentNormal;

} ).once() )();

/**
 * This class can be used for applying normals maps to materials.
 *
 * ```js
 * material.normalNode = normalMap( texture( normalTex ) );
 * ```
 *
 * @augments Node
 */
class NormalMapNode extends Node {

	static get type() {

		return 'NormalMapNode';

	}

	/**
	 * Constructs a new normal map node.
	 *
	 * @param {Node<vec3>} node - Represents the normal map data.
	 * @param {?Node<vec2>} [scaleNode=null] - Controls the intensity of the effect.
	 */
	constructor( node, scaleNode = null ) {

		super( 'vec3' );

		/**
		 * Represents the normal map data.
		 *
		 * @type {Node<vec3>}
		 */
		this.node = node;

		/**
		 * Controls the intensity of the effect.
		 *
		 * @type {?Node<vec2>}
		 * @default null
		 */
		this.scaleNode = scaleNode;

		/**
		 * The normal map type.
		 *
		 * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)}
		 * @default TangentSpaceNormalMap
		 */
		this.normalMapType = TangentSpaceNormalMap;

		/**
		 * Controls how to unpack the sampled normal map values.
		 *
		 * @type {string}
		 * @default NoNormalPacking
		 */
		this.unpackNormalMode = NoNormalPacking;

	}

	setup( builder ) {

		const { normalMapType, scaleNode, unpackNormalMode } = this;

		let normalMap = this.node.mul( 2.0 ).sub( 1.0 );

		if ( normalMapType === TangentSpaceNormalMap ) {

			if ( unpackNormalMode === NormalRGPacking ) {

				normalMap = unpackNormal( normalMap.xy );

			} else if ( unpackNormalMode === NormalGAPacking ) {

				normalMap = unpackNormal( normalMap.yw );

			} else if ( unpackNormalMode !== NoNormalPacking ) {

				error( `THREE.NodeMaterial: Unexpected unpack normal mode: ${ unpackNormalMode }` );

			}

		} else {

			if ( unpackNormalMode !== NoNormalPacking ) {

				error( `THREE.NodeMaterial: Normal map type '${ normalMapType }' is not compatible with unpack normal mode '${ unpackNormalMode }'` );

			}

		}

		if ( scaleNode !== null ) {

			let scale = scaleNode;

			if ( builder.isFlatShading() === true ) {

				scale = negateOnBackSide( scale );

			}

			normalMap = vec3( normalMap.xy.mul( scale ), normalMap.z );

		}

		let output = null;

		if ( normalMapType === ObjectSpaceNormalMap ) {

			output = transformNormalToView( normalMap );

		} else if ( normalMapType === TangentSpaceNormalMap ) {

			output = TBNViewMatrix.mul( normalMap ).normalize();

		} else {

			error( `NodeMaterial: Unsupported normal map type: ${ normalMapType }` );

			output = normalView; // Fallback to default normal view

		}

		return output;

	}

}

/**
 * TSL function for creating a normal map node.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} node - Represents the normal map data.
 * @param {?Node<vec2>} [scaleNode=null] - Controls the intensity of the effect.
 * @returns {NormalMapNode}
 */
const normalMap = /*@__PURE__*/ nodeProxy( NormalMapNode ).setParameterLength( 1, 2 );

// Bump Mapping Unparametrized Surfaces on the GPU by Morten S. Mikkelsen
// https://mmikk.github.io/papers3d/mm_sfgrad_bump.pdf

const dHdxy_fwd = /*@__PURE__*/ Fn( ( { textureNode, bumpScale } ) => {

	// It's used to preserve the same TextureNode instance
	const sampleTexture = ( callback ) => textureNode.isolate().context( { getUV: ( texNode ) => callback( texNode.uvNode || uv$1() ), forceUVContext: true } );

	const Hll = float( sampleTexture( ( uvNode ) => uvNode ) );

	return vec2(
		float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdx() ) ) ).sub( Hll ),
		float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdy() ) ) ).sub( Hll )
	).mul( bumpScale );

} );

// Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)

const perturbNormalArb = /*@__PURE__*/ Fn( ( inputs ) => {

	const { surf_pos, surf_norm, dHdxy } = inputs;

	// normalize is done to ensure that the bump map looks the same regardless of the texture's scale
	const vSigmaX = surf_pos.dFdx().normalize();
	const vSigmaY = surf_pos.dFdy().normalize();
	const vN = surf_norm; // normalized

	const R1 = vSigmaY.cross( vN );
	const R2 = vN.cross( vSigmaX );

	const fDet = vSigmaX.dot( R1 ).mul( faceDirection );

	const vGrad = fDet.sign().mul( dHdxy.x.mul( R1 ).add( dHdxy.y.mul( R2 ) ) );

	return fDet.abs().mul( surf_norm ).sub( vGrad ).normalize();

} );

/**
 * This class can be used for applying bump maps to materials.
 *
 * ```js
 * material.normalNode = bumpMap( texture( bumpTex ) );
 * ```
 *
 * @augments Node
 */
class BumpMapNode extends Node {

	static get type() {

		return 'BumpMapNode';

	}

	/**
	 * Constructs a new bump map node.
	 *
	 * @param {Node<float>} textureNode - Represents the bump map data.
	 * @param {?Node<float>} [scaleNode=null] - Controls the intensity of the bump effect.
	 * @param {Node<vec3>} [normalNode=normalView] - The view-space normal to perturb.
	 */
	constructor( textureNode, scaleNode = null, normalNode = normalView ) {

		super( 'vec3' );

		/**
		 * Represents the bump map data.
		 *
		 * @type {Node<float>}
		 */
		this.textureNode = textureNode;

		/**
		 * Controls the intensity of the bump effect.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.scaleNode = scaleNode;

		/**
		 * The view-space normal to perturb.
		 *
		 * @type {Node<vec3>}
		 * @default normalView
		 */
		this.normalNode = normalNode;

	}

	setup( builder ) {

		// Screen-space derivatives are unreliable on thin lines, so the bump
		// effect is disabled for wireframe rendering.

		if ( builder.material.wireframe === true ) return this.normalNode;

		const bumpScale = this.scaleNode !== null ? this.scaleNode : 1;
		const dHdxy = dHdxy_fwd( { textureNode: this.textureNode, bumpScale } );

		return perturbNormalArb( {
			surf_pos: positionView,
			surf_norm: this.normalNode,
			dHdxy
		} );

	}

}

/**
 * TSL function for creating a bump map node.
 *
 * @tsl
 * @function
 * @param {Node<float>} textureNode - Represents the bump map data.
 * @param {?Node<float>} [scaleNode=null] - Controls the intensity of the bump effect.
 * @param {Node<vec3>} [normalNode=normalView] - The view-space normal to perturb.
 * @returns {BumpMapNode}
 */
const bumpMap = /*@__PURE__*/ nodeProxy( BumpMapNode ).setParameterLength( 1, 3 );

const _propertyCache = new Map();

/**
 * This class should simplify the node access to material properties.
 * It internal uses reference nodes to make sure  changes to material
 * properties are automatically reflected to predefined TSL objects
 * like e.g. `materialColor`.
 *
 * @augments Node
 */
class MaterialNode extends Node {

	static get type() {

		return 'MaterialNode';

	}

	/**
	 * Constructs a new material node.
	 *
	 * @param {string} scope - The scope defines what kind of material property is referred by the node.
	 */
	constructor( scope ) {

		super();

		/**
		 * The scope defines what material property is referred by the node.
		 *
		 * @type {string}
		 */
		this.scope = scope;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Returns a cached reference node for the given property and type.
	 *
	 * @param {string} property - The name of the material property.
	 * @param {string} type - The uniform type of the property.
	 * @return {MaterialReferenceNode} A material reference node representing the property access.
	 */
	getCache( property, type ) {

		let node = _propertyCache.get( property );

		if ( node === undefined ) {

			node = materialReference( property, type );

			_propertyCache.set( property, node );

		}

		return node;

	}

	/**
	 * Returns a float-typed material reference node for the given property name.
	 *
	 * @param {string} property - The name of the material property.
	 * @return {MaterialReferenceNode<float>} A material reference node representing the property access.
	 */
	getFloat( property ) {

		return this.getCache( property, 'float' );

	}

	/**
	 * Returns a color-typed material reference node for the given property name.
	 *
	 * @param {string} property - The name of the material property.
	 * @return {MaterialReferenceNode<color>} A material reference node representing the property access.
	 */
	getColor( property ) {

		return this.getCache( property, 'color' );

	}

	/**
	 * Returns a texture-typed material reference node for the given property name.
	 *
	 * @param {string} property - The name of the material property.
	 * @return {MaterialReferenceNode} A material reference node representing the property access.
	 */
	getTexture( property ) {

		return this.getCache( property === 'map' ? 'map' : property + 'Map', 'texture' );

	}

	/**
	 * The node setup is done depending on the selected scope. Multiple material properties
	 * might be grouped into a single node composition if they logically belong together.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node} The node representing the selected scope.
	 */
	setup( builder ) {

		const material = builder.context.material;
		const scope = this.scope;

		let node = null;

		if ( scope === MaterialNode.COLOR ) {

			const colorNode = material.color !== undefined ? this.getColor( scope ) : vec3();

			if ( material.map && material.map.isTexture === true ) {

				node = colorNode.mul( this.getTexture( 'map' ) );

			} else {

				node = colorNode;

			}

		} else if ( scope === MaterialNode.OPACITY ) {

			const opacityNode = this.getFloat( scope );

			if ( material.alphaMap && material.alphaMap.isTexture === true ) {

				node = opacityNode.mul( this.getTexture( 'alpha' ) );

			} else {

				node = opacityNode;

			}

		} else if ( scope === MaterialNode.SPECULAR_STRENGTH ) {

			if ( material.specularMap && material.specularMap.isTexture === true ) {

				node = this.getTexture( 'specular' ).r;

			} else {

				node = float( 1 );

			}

		} else if ( scope === MaterialNode.SPECULAR_INTENSITY ) {

			const specularIntensityNode = this.getFloat( scope );

			if ( material.specularIntensityMap && material.specularIntensityMap.isTexture === true ) {

				node = specularIntensityNode.mul( this.getTexture( scope ).a );

			} else {

				node = specularIntensityNode;

			}

		} else if ( scope === MaterialNode.SPECULAR_COLOR ) {

			const specularColorNode = this.getColor( scope );

			if ( material.specularColorMap && material.specularColorMap.isTexture === true ) {

				node = specularColorNode.mul( this.getTexture( scope ).rgb );

			} else {

				node = specularColorNode;

			}

		} else if ( scope === MaterialNode.ROUGHNESS ) { // TODO: cleanup similar branches

			const roughnessNode = this.getFloat( scope );

			if ( material.roughnessMap && material.roughnessMap.isTexture === true ) {

				node = roughnessNode.mul( this.getTexture( scope ).g );

			} else {

				node = roughnessNode;

			}

		} else if ( scope === MaterialNode.DIFFUSE_ROUGHNESS ) {

			const diffuseRoughnessNode = this.getFloat( scope );

			if ( material.diffuseRoughnessMap && material.diffuseRoughnessMap.isTexture === true ) {

				node = diffuseRoughnessNode.mul( this.getTexture( scope ).r );

			} else {

				node = diffuseRoughnessNode;

			}

		} else if ( scope === MaterialNode.METALNESS ) {

			const metalnessNode = this.getFloat( scope );

			if ( material.metalnessMap && material.metalnessMap.isTexture === true ) {

				node = metalnessNode.mul( this.getTexture( scope ).b );

			} else {

				node = metalnessNode;

			}

		} else if ( scope === MaterialNode.EMISSIVE ) {

			const emissiveIntensityNode = this.getFloat( 'emissiveIntensity' );
			const emissiveNode = this.getColor( scope ).mul( emissiveIntensityNode );

			if ( material.emissiveMap && material.emissiveMap.isTexture === true ) {

				node = emissiveNode.mul( this.getTexture( scope ) );

			} else {

				node = emissiveNode;

			}

		} else if ( scope === MaterialNode.NORMAL ) {

			if ( material.normalMap ) {

				node = normalMap( this.getTexture( 'normal' ), this.getCache( 'normalScale', 'vec2' ) );
				node.normalMapType = material.normalMapType;

				if ( material.normalMap.format == RGFormat || material.normalMap.format == RED_GREEN_RGTC2_Format || material.normalMap.format == RG11_EAC_Format ) {

					node.unpackNormalMode = NormalRGPacking;

				}

			} else if ( material.bumpMap ) {

				node = bumpMap( this.getTexture( 'bump' ).r, this.getFloat( 'bumpScale' ) );

			} else {

				node = normalView;

			}

		} else if ( scope === MaterialNode.CLEARCOAT ) {

			const clearcoatNode = this.getFloat( scope );

			if ( material.clearcoatMap && material.clearcoatMap.isTexture === true ) {

				node = clearcoatNode.mul( this.getTexture( scope ).r );

			} else {

				node = clearcoatNode;

			}

		} else if ( scope === MaterialNode.CLEARCOAT_ROUGHNESS ) {

			const clearcoatRoughnessNode = this.getFloat( scope );

			if ( material.clearcoatRoughnessMap && material.clearcoatRoughnessMap.isTexture === true ) {

				node = clearcoatRoughnessNode.mul( this.getTexture( scope ).g );

			} else {

				node = clearcoatRoughnessNode;

			}

		} else if ( scope === MaterialNode.CLEARCOAT_NORMAL ) {

			if ( material.clearcoatNormalMap ) {

				node = normalMap( this.getTexture( scope ), this.getCache( scope + 'Scale', 'vec2' ) );

			} else {

				node = normalView;

			}

		} else if ( scope === MaterialNode.SHEEN ) {

			const sheenNode = this.getColor( 'sheenColor' ).mul( this.getFloat( 'sheen' ) ); // Move this mul() to CPU

			if ( material.sheenColorMap && material.sheenColorMap.isTexture === true ) {

				node = sheenNode.mul( this.getTexture( 'sheenColor' ).rgb );

			} else {

				node = sheenNode;

			}

		} else if ( scope === MaterialNode.SHEEN_ROUGHNESS ) {

			const sheenRoughnessNode = this.getFloat( scope );

			if ( material.sheenRoughnessMap && material.sheenRoughnessMap.isTexture === true ) {

				node = sheenRoughnessNode.mul( this.getTexture( scope ).a );

			} else {

				node = sheenRoughnessNode;

			}

			node = node.clamp( 0.0001, 1.0 );

		} else if ( scope === MaterialNode.ANISOTROPY ) {

			if ( material.anisotropyMap && material.anisotropyMap.isTexture === true ) {

				const anisotropyPolar = this.getTexture( scope );
				const anisotropyMat = mat2( materialAnisotropyVector.x, materialAnisotropyVector.y, materialAnisotropyVector.y.negate(), materialAnisotropyVector.x );

				node = anisotropyMat.mul( anisotropyPolar.rg.mul( 2.0 ).sub( vec2( 1.0 ) ).normalize().mul( anisotropyPolar.b ) );

			} else {

				node = materialAnisotropyVector;

			}

		} else if ( scope === MaterialNode.IRIDESCENCE_THICKNESS ) {

			const iridescenceThicknessMaximum = this.getFloat( 'iridescenceThicknessMaximum' );

			if ( material.iridescenceThicknessMap ) {

				const iridescenceThicknessMinimum = this.getFloat( 'iridescenceThicknessMinimum' );

				node = iridescenceThicknessMaximum.sub( iridescenceThicknessMinimum ).mul( this.getTexture( scope ).g ).add( iridescenceThicknessMinimum );

			} else {

				node = iridescenceThicknessMaximum;

			}

		} else if ( scope === MaterialNode.TRANSMISSION ) {

			const transmissionNode = this.getFloat( scope );

			if ( material.transmissionMap ) {

				node = transmissionNode.mul( this.getTexture( scope ).r );

			} else {

				node = transmissionNode;

			}

		} else if ( scope === MaterialNode.THICKNESS ) {

			const thicknessNode = this.getFloat( scope );

			if ( material.thicknessMap ) {

				node = thicknessNode.mul( this.getTexture( scope ).g );

			} else {

				node = thicknessNode;

			}

		} else if ( scope === MaterialNode.IOR ) {

			node = this.getFloat( scope );

		} else if ( scope === MaterialNode.LIGHT_MAP ) {

			if ( material.lightMap ) {

				node = this.getTexture( scope ).rgb.mul( this.getFloat( 'lightMapIntensity' ) );

			} else {

				node = vec3( 0.0 );

			}

		} else if ( scope === MaterialNode.AO ) {

			if ( material.aoMap ) {

				node = this.getTexture( scope ).r.sub( 1.0 ).mul( this.getFloat( 'aoMapIntensity' ) ).add( 1.0 );

			} else {

				node = float( 1.0 );

			}

		} else if ( scope === MaterialNode.LINE_DASH_OFFSET ) {

			node = ( material.dashOffset ) ? this.getFloat( scope ) : float( 0 );

		} else {

			const outputType = this.getNodeType( builder );

			node = this.getCache( scope, outputType );

		}

		return node;

	}

}

MaterialNode.ALPHA_TEST = 'alphaTest';
MaterialNode.COLOR = 'color';
MaterialNode.OPACITY = 'opacity';
MaterialNode.SHININESS = 'shininess';
MaterialNode.SPECULAR = 'specular';
MaterialNode.SPECULAR_STRENGTH = 'specularStrength';
MaterialNode.SPECULAR_INTENSITY = 'specularIntensity';
MaterialNode.SPECULAR_COLOR = 'specularColor';
MaterialNode.REFLECTIVITY = 'reflectivity';
MaterialNode.ROUGHNESS = 'roughness';
MaterialNode.DIFFUSE_ROUGHNESS = 'diffuseRoughness';
MaterialNode.METALNESS = 'metalness';
MaterialNode.NORMAL = 'normal';
MaterialNode.CLEARCOAT = 'clearcoat';
MaterialNode.CLEARCOAT_ROUGHNESS = 'clearcoatRoughness';
MaterialNode.CLEARCOAT_NORMAL = 'clearcoatNormal';
MaterialNode.EMISSIVE = 'emissive';
MaterialNode.ROTATION = 'rotation';
MaterialNode.SHEEN = 'sheen';
MaterialNode.SHEEN_ROUGHNESS = 'sheenRoughness';
MaterialNode.ANISOTROPY = 'anisotropy';
MaterialNode.IRIDESCENCE = 'iridescence';
MaterialNode.IRIDESCENCE_IOR = 'iridescenceIOR';
MaterialNode.IRIDESCENCE_THICKNESS = 'iridescenceThickness';
MaterialNode.IOR = 'ior';
MaterialNode.TRANSMISSION = 'transmission';
MaterialNode.THICKNESS = 'thickness';
MaterialNode.ATTENUATION_DISTANCE = 'attenuationDistance';
MaterialNode.ATTENUATION_COLOR = 'attenuationColor';
MaterialNode.LINE_SCALE = 'scale';
MaterialNode.LINE_DASH_SIZE = 'dashSize';
MaterialNode.LINE_GAP_SIZE = 'gapSize';
MaterialNode.LINE_WIDTH = 'linewidth';
MaterialNode.LINE_DASH_OFFSET = 'dashOffset';
MaterialNode.POINT_SIZE = 'size';
MaterialNode.DISPERSION = 'dispersion';
MaterialNode.RETROREFLECTIVITY = 'retroreflectivity';
MaterialNode.LIGHT_MAP = 'light';
MaterialNode.AO = 'ao';

/**
 * TSL object that represents alpha test of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialAlphaTest = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST );

/**
 * TSL object that represents the diffuse color of the current material.
 * The value is composed via `color` * `map`.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const materialColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.COLOR );

/**
 * TSL object that represents the shininess of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialShininess = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHININESS );

/**
 * TSL object that represents the emissive color of the current material.
 * The value is composed via `emissive` * `emissiveIntensity` * `emissiveMap`.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const materialEmissive = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.EMISSIVE );

/**
 * TSL object that represents the opacity of the current material.
 * The value is composed via `opacity` * `alphaMap`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialOpacity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.OPACITY );

/**
 * TSL object that represents the specular of the current material.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const materialSpecular = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR );

/**
 * TSL object that represents the specular intensity of the current material.
 * The value is composed via `specularIntensity` * `specularMap.a`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialSpecularIntensity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_INTENSITY );

/**
 * TSL object that represents the specular color of the current material.
 * The value is composed via `specularColor` * `specularMap.rgb`.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const materialSpecularColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_COLOR );

/**
 * TSL object that represents the specular strength of the current material.
 * The value is composed via `specularMap.r`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialSpecularStrength = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_STRENGTH );

/**
 * TSL object that represents the reflectivity of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialReflectivity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.REFLECTIVITY );

/**
 * TSL object that represents the roughness of the current material.
 * The value is composed via `roughness` * `roughnessMap.g`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );

/**
 * TSL object that represents the diffuse roughness of the current material.
 * The value is composed via `diffuseRoughness` * `diffuseRoughnessMap.r`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialDiffuseRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.DIFFUSE_ROUGHNESS );

/**
 * TSL object that represents the metalness of the current material.
 * The value is composed via `metalness` * `metalnessMap.b`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialMetalness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.METALNESS );

/**
 * TSL object that represents the normal of the current material.
 * The value will be either `normalMap` * `normalScale`, `bumpMap` * `bumpScale` or `normalView`.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const materialNormal = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.NORMAL );

/**
 * TSL object that represents the clearcoat of the current material.
 * The value is composed via `clearcoat` * `clearcoatMap.r`
 *
 * @tsl
 * @type {Node<float>}
 */
const materialClearcoat = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT );

/**
 * TSL object that represents the clearcoat roughness of the current material.
 * The value is composed via `clearcoatRoughness` * `clearcoatRoughnessMap.r`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialClearcoatRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_ROUGHNESS );

/**
 * TSL object that represents the clearcoat normal of the current material.
 * The value will be either `clearcoatNormalMap` or `normalView`.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const materialClearcoatNormal = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_NORMAL );

/**
 * TSL object that represents the rotation of the current sprite material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialRotation = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ROTATION );

/**
 * TSL object that represents the sheen color of the current material.
 * The value is composed via `sheen` * `sheenColor` * `sheenColorMap`.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const materialSheen = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHEEN );

/**
 * TSL object that represents the sheen roughness of the current material.
 * The value is composed via `sheenRoughness` * `sheenRoughnessMap.a`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialSheenRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHEEN_ROUGHNESS );

/**
 * TSL object that represents the anisotropy of the current material.
 *
 * @tsl
 * @type {Node<vec2>}
 */
const materialAnisotropy = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ANISOTROPY );

/**
 * TSL object that represents the iridescence of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialIridescence = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE );

/**
 * TSL object that represents the iridescence IOR of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialIridescenceIOR = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_IOR );

/**
 * TSL object that represents the iridescence thickness of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialIridescenceThickness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_THICKNESS );

/**
 * TSL object that represents the transmission of the current material.
 * The value is composed via `transmission` * `transmissionMap.r`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialTransmission = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.TRANSMISSION );

/**
 * TSL object that represents the thickness of the current material.
 * The value is composed via `thickness` * `thicknessMap.g`.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialThickness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.THICKNESS );

/**
 * TSL object that represents the IOR of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialIOR = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IOR );

/**
 * TSL object that represents the attenuation distance of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialAttenuationDistance = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_DISTANCE );

/**
 * TSL object that represents the attenuation color of the current material.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const materialAttenuationColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_COLOR );

/**
 * TSL object that represents the scale of the current dashed line material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialLineScale = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_SCALE );

/**
 * TSL object that represents the dash size of the current dashed line material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialLineDashSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_SIZE );

/**
 * TSL object that represents the gap size of the current dashed line material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialLineGapSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_GAP_SIZE );

/**
 * TSL object that represents the line width of the current line material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialLineWidth = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_WIDTH );

/**
 * TSL object that represents the dash offset of the current line material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialLineDashOffset = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_OFFSET );

/**
 * TSL object that represents the point size of the current points material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialPointSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.POINT_SIZE );

/**
 * TSL object that represents the dispersion of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialDispersion = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.DISPERSION );

/**
 * TSL object that represents the retroreflective strength of the current material.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialRetroreflectivity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.RETROREFLECTIVITY );

/**
 * TSL object that represents the light map of the current material.
 * The value is composed via `lightMapIntensity` * `lightMap.rgb`.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const materialLightMap = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LIGHT_MAP );

/**
 * TSL object that represents the ambient occlusion map of the current material.
 * The value is composed via `aoMap.r` - 1 * `aoMapIntensity` + 1.
 *
 * @tsl
 * @type {Node<float>}
 */
const materialAO = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.AO );

/**
 * TSL object that represents the anisotropy vector of the current material.
 *
 * @tsl
 * @type {Node<vec2>}
 */
const materialAnisotropyVector = /*@__PURE__*/ uniform( new Vector2() ).onReference( function ( frame ) {

	return frame.material;

} ).onRenderUpdate( function ( { material } ) {

	this.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) );

} );

/**
 * TSL object that represents the position in clip space after the model-view-projection transform of the current rendered object.
 *
 * @tsl
 * @type {VaryingNode<vec4>}
 */
const modelViewProjection = /*@__PURE__*/ ( Fn( ( builder ) => {

	return builder.context.setupModelViewProjection();

}, 'vec4' ).once() )().toVarying( 'v_modelViewProjection' );

/**
 * EventNode is a node that executes a callback during specific update phases.
 *
 * @augments Node
 */
class EventNode extends Node {

	static get type() {

		return 'EventNode';

	}

	/**
	 * Creates an EventNode.
	 *
	 * @param {string} eventType - The type of event
	 * @param {Function} callback - The callback to execute on update.
	 */
	constructor( eventType, callback ) {

		super( 'void' );

		this.eventType = eventType;
		this.callback = callback;

		if ( eventType === EventNode.OBJECT ) {

			this.updateType = NodeUpdateType.OBJECT;

		} else if ( eventType === EventNode.MATERIAL ) {

			this.updateType = NodeUpdateType.RENDER;

		} else if ( eventType === EventNode.FRAME ) {

			this.updateType = NodeUpdateType.FRAME;

		} else if ( eventType === EventNode.AFTER_OBJECT ) {

			this.updateAfterType = NodeUpdateType.OBJECT;

		} else if ( eventType === EventNode.BEFORE_OBJECT ) {

			this.updateBeforeType = NodeUpdateType.OBJECT;

		} else if ( eventType === EventNode.BEFORE_MATERIAL ) {

			this.updateBeforeType = NodeUpdateType.RENDER;

		} else if ( eventType === EventNode.BEFORE_FRAME ) {

			this.updateBeforeType = NodeUpdateType.FRAME;

		}

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	setup( builder ) {

		const { eventType, callback } = this;

		if ( eventType === EventNode.BEFORE_RENDER_PIPELINE ) {

			const callbacks = builder.context.onBeforePipelineCallbacks;

			if ( callbacks ) {

				callbacks.push( callback );

			}

		} else if ( eventType === EventNode.AFTER_RENDER_PIPELINE ) {

			const callbacks = builder.context.onAfterPipelineCallbacks;

			if ( callbacks ) {

				callbacks.push( callback );

			}

		}

		return super.setup( builder );

	}

	update( frame ) {

		this.callback( frame );

	}

	updateBefore( frame ) {

		this.callback( frame );

	}

	updateAfter( frame ) {

		this.callback( frame );

	}

}

EventNode.OBJECT = 'object';
EventNode.MATERIAL = 'material';
EventNode.FRAME = 'frame';
EventNode.AFTER_OBJECT = 'afterObject';
EventNode.BEFORE_OBJECT = 'beforeObject';
EventNode.BEFORE_MATERIAL = 'beforeMaterial';
EventNode.BEFORE_FRAME = 'beforeFrame';
EventNode.BEFORE_RENDER_PIPELINE = 'beforeRenderPipeline';
EventNode.AFTER_RENDER_PIPELINE = 'afterRenderPipeline';

/**
 * Helper to create an EventNode and add it to the stack.
 *
 * @param {string} type - The event type.
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const createEvent = ( type, callback ) => new EventNode( type, callback ).toStack();

/**
 * Creates an event that triggers a function every time an object (Mesh|Sprite) is rendered.
 *
 * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
 *
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const OnObjectUpdate = ( callback ) => createEvent( EventNode.OBJECT, callback );

/**
 * Creates an event that triggers a function when the first object that uses the material is rendered.
 *
 * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
 *
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const OnMaterialUpdate = ( callback ) => createEvent( EventNode.MATERIAL, callback );

/**
 * Creates an event that triggers a function every frame.
 *
 * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
 *
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const OnFrameUpdate = ( callback ) => createEvent( EventNode.FRAME, callback );

/**
 * Creates an event that triggers a function every time an object (Mesh|Sprite) has been rendered.
 *
 * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
 *
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const OnAfterObjectUpdate = ( callback ) => createEvent( EventNode.AFTER_OBJECT, callback );

/**
 * Creates an event that triggers a function before an object (Mesh|Sprite) is updated.
 *
 * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
 *
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const OnBeforeObjectUpdate = ( callback ) => createEvent( EventNode.BEFORE_OBJECT, callback );

/**
 * Creates an event that triggers a function before the material is updated.
 *
 * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
 *
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const OnBeforeMaterialUpdate = ( callback ) => createEvent( EventNode.BEFORE_MATERIAL, callback );

/**
 * Creates an event that triggers a function before every frame.
 *
 * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
 *
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const OnBeforeFrameUpdate = ( callback ) => createEvent( EventNode.BEFORE_FRAME, callback );

/**
 * Creates an event that triggers a function before the render pipeline is rendered.
 *
 * The node must be part of a node chain that is used as the output of a `RenderPipeline`,
 * otherwise the event is ignored.
 *
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const OnBeforeRenderPipeline = ( callback ) => createEvent( EventNode.BEFORE_RENDER_PIPELINE, callback );

/**
 * Creates an event that triggers a function after the render pipeline is rendered.
 *
 * The node must be part of a node chain that is used as the output of a `RenderPipeline`,
 * otherwise the event is ignored.
 *
 * @param {Function} callback - The callback function.
 * @returns {EventNode}
 */
const OnAfterRenderPipeline = ( callback ) => createEvent( EventNode.AFTER_RENDER_PIPELINE, callback );

/**
 * This class enables element access on instances of {@link StorageBufferNode}.
 * In most cases, it is indirectly used when accessing elements with the
 * {@link StorageBufferNode#element} method.
 *
 * ```js
 * const position = positionStorage.element( instanceIndex );
 * ```
 *
 * @augments ArrayElementNode
 */
class StorageArrayElementNode extends ArrayElementNode {

	static get type() {

		return 'StorageArrayElementNode';

	}

	/**
	 * Constructs storage buffer element node.
	 *
	 * @param {StorageBufferNode} storageBufferNode - The storage buffer node.
	 * @param {Node} indexNode - The index node that defines the element access.
	 */
	constructor( storageBufferNode, indexNode ) {

		super( storageBufferNode, indexNode );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isStorageArrayElementNode = true;

	}

	/**
	 * The storage buffer node.
	 *
	 * @param {Node} value
	 * @type {StorageBufferNode}
	 */
	set storageBufferNode( value ) {

		this.node = value;

	}

	get storageBufferNode() {

		return this.node;

	}

	getMemberType( builder, name ) {

		const structTypeNode = this.storageBufferNode.structTypeNode;

		if ( structTypeNode ) {

			return structTypeNode.getMemberType( builder, name );

		}

		return 'void';

	}

	setup( builder ) {

		if ( builder.isAvailable( 'storageBuffer' ) === false ) {

			if ( this.node.isPBO === true ) {

				builder.setupPBO( this.node );

			}

		}

		return super.setup( builder );

	}

	generate( builder, output ) {

		let snippet;

		const isAssignContext = builder.isContextAssign();

		//

		if ( builder.isAvailable( 'storageBuffer' ) === false ) {

			if ( this.node.isPBO === true && isAssignContext !== true && ( this.node.value.isInstancedBufferAttribute || builder.shaderStage !== 'compute' ) ) {

				snippet = builder.generatePBO( this );

			} else {

				snippet = this.node.build( builder );

			}

		} else {

			snippet = super.generate( builder );

		}

		if ( isAssignContext !== true ) {

			const type = this.getNodeType( builder );

			snippet = builder.format( snippet, type, output );

		}

		return snippet;

	}

}

/**
 * TSL function for creating a storage element node.
 *
 * @tsl
 * @function
 * @param {StorageBufferNode} storageBufferNode - The storage buffer node.
 * @param {Node} indexNode - The index node that defines the element access.
 * @returns {StorageArrayElementNode}
 */
const storageElement = /*@__PURE__*/ nodeProxy( StorageArrayElementNode ).setParameterLength( 2 );

/**
 * This node is used in context of compute shaders and allows to define a
 * storage buffer for data. A typical workflow is to create instances of
 * this node with the convenience functions `attributeArray()` or `instancedArray()`,
 * setup up a compute shader that writes into the buffers and then convert
 * the storage buffers to attribute nodes for rendering.
 *
 * ```js
 * const positionBuffer = instancedArray( particleCount, 'vec3' ); // the storage buffer node
 *
 * const computeInit = Fn( () => { // the compute shader
 *
 * 	const position = positionBuffer.element( instanceIndex );
 *
 * 	// compute position data
 *
 * 	position.x = 1;
 * 	position.y = 1;
 * 	position.z = 1;
 *
 * } )().compute( particleCount );
 *
 * const particleMaterial = new THREE.SpriteNodeMaterial();
 * particleMaterial.positionNode = positionBuffer.toAttribute();
 *
 * renderer.computeAsync( computeInit );
 *
 * ```
 *
 * @augments BufferNode
 */
class StorageBufferNode extends BufferNode {

	static get type() {

		return 'StorageBufferNode';

	}

	/**
	 * Constructs a new storage buffer node.
	 *
	 * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data.
	 * @param {?(string|Struct)} [bufferType=null] - The buffer type (e.g. `'vec3'`).
	 * @param {number} [bufferCount=0] - The buffer count.
	 */
	constructor( value, bufferType = null, bufferCount = 0 ) {

		let nodeType, structTypeNode = null;

		if ( bufferType && bufferType.isStructTypeNode ) {

			nodeType = 'struct';
			structTypeNode = bufferType;

			if ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) {

				bufferCount = value.count;

			}

		} else if ( bufferType === null && ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) ) {

			nodeType = getTypeFromLength( value.itemSize );
			bufferCount = value.count;

		} else {

			nodeType = bufferType;

		}

		super( value, nodeType, bufferCount );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isStorageBufferNode = true;


		/**
		 * The buffer struct type.
		 *
		 * @type {?StructTypeNode}
		 * @default null
		 */
		this.structTypeNode = structTypeNode;

		/**
		 * The access type of the texture node.
		 *
		 * @type {string}
		 * @default 'readWrite'
		 */
		this.access = NodeAccess.READ_WRITE;

		/**
		 * Whether the node is atomic or not.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.isAtomic = false;

		/**
		 * Whether the node represents a PBO or not.
		 * Only relevant for WebGL.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.isPBO = false;

		/**
		 * A reference to the internal buffer attribute node.
		 *
		 * @private
		 * @type {?BufferAttributeNode}
		 * @default null
		 */
		this._attribute = null;

		/**
		 * A reference to the internal varying node.
		 *
		 * @private
		 * @type {?VaryingNode}
		 * @default null
		 */
		this._varying = null;

		/**
		 * `StorageBufferNode` sets this property to `true` by default.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

		if ( value.isStorageBufferAttribute !== true && value.isStorageInstancedBufferAttribute !== true ) {

			// TODO: Improve it, possibly adding a new property to the BufferAttribute to identify it as a storage buffer read-only attribute in Renderer

			if ( value.isInstancedBufferAttribute ) value.isStorageInstancedBufferAttribute = true;
			else value.isStorageBufferAttribute = true;

		}

	}

	/**
	 * This method is overwritten since the buffer data might be shared
	 * and thus the hash should be shared as well.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The hash.
	 */
	getHash( builder ) {

		let id;

		if ( this.bufferCount === 0 ) {

			let bufferData = builder.globalCache.getData( this.value );

			if ( bufferData === undefined ) {

				bufferData = {
					node: this
				};

				builder.globalCache.setData( this.value, bufferData );

			}

			id = bufferData.node.id;

		} else {

			id = this.id;

		}

		return String( id );

	}

	/**
	 * Overwrites the default implementation to return a fixed value `'indirectStorageBuffer'` or `'storageBuffer'`.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( /*builder*/ ) {

		return this.value.isIndirectStorageBufferAttribute ? 'indirectStorageBuffer' : 'storageBuffer';

	}

	/**
	 * Enables element access with the given index node.
	 *
	 * @param {IndexNode} indexNode - The index node.
	 * @return {StorageArrayElementNode} A node representing the element access.
	 */
	element( indexNode ) {

		return storageElement( this, indexNode );

	}

	/**
	 * Defines whether this node is a PBO or not. Only relevant for WebGL.
	 *
	 * @param {boolean} value - The value so set.
	 * @return {StorageBufferNode} A reference to this node.
	 */
	setPBO( value ) {

		this.isPBO = value;

		return this;

	}

	/**
	 * Returns the `isPBO` value.
	 *
	 * @return {boolean} Whether the node represents a PBO or not.
	 */
	getPBO() {

		return this.isPBO;

	}

	/**
	 * Defines the node access.
	 *
	 * @param {string} value - The node access.
	 * @return {StorageBufferNode} A reference to this node.
	 */
	setAccess( value ) {

		this.access = value;

		return this;

	}

	/**
	 * Convenience method for configuring a read-only node access.
	 *
	 * @return {StorageBufferNode} A reference to this node.
	 */
	toReadOnly() {

		return this.setAccess( NodeAccess.READ_ONLY );

	}

	/**
	 * Defines whether the node is atomic or not.
	 *
	 * @param {boolean} value - The atomic flag.
	 * @return {StorageBufferNode} A reference to this node.
	 */
	setAtomic( value ) {

		this.isAtomic = value;

		return this;

	}

	/**
	 * Convenience method for making this node atomic.
	 *
	 * @return {StorageBufferNode} A reference to this node.
	 */
	toAtomic() {

		return this.setAtomic( true );

	}

	/**
	 * Returns attribute data for this storage buffer node.
	 *
	 * @return {{attribute: BufferAttributeNode, varying: VaryingNode}} The attribute data.
	 */
	getAttributeData() {

		if ( this._attribute === null ) {

			this._attribute = bufferAttribute( this.value );
			this._varying = varying( this._attribute );

		}

		return {
			attribute: this._attribute,
			varying: this._varying
		};

	}

	/**
	 * This method is overwritten since the node type is inferred from the struct type if present.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		if ( this.structTypeNode !== null ) {

			return this.structTypeNode.getNodeType( builder );

		}

		return super.generateNodeType( builder );

	}

	/**
	 * Returns the type of a member of the struct.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} name - The name of the member.
	 * @return {string} The type of the member.
	 */
	getMemberType( builder, name ) {

		if ( this.structTypeNode !== null ) {

			return this.structTypeNode.getMemberType( builder, name );

		}

		return 'void';

	}

	/**
	 * Generates the code snippet of the storage buffer node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The generated code snippet.
	 */
	generate( builder ) {

		if ( this.structTypeNode !== null ) this.structTypeNode.build( builder );

		if ( builder.isAvailable( 'storageBuffer' ) || builder.isAvailable( 'indirectStorageBuffer' ) ) {

			return super.generate( builder );

		}

		const { attribute, varying } = this.getAttributeData();

		const output = varying.build( builder );

		builder.registerTransform( output, attribute );

		return builder.format( output, attribute.getNodeType( builder ), this.getNodeType( builder ) );

	}

}

/**
 * TSL function for creating a storage buffer node.
 *
 * @tsl
 * @function
 * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data.
 * @param {?(string|Struct)} [type=null] - The buffer type (e.g. `'vec3'`).
 * @param {number} [count=0] - The buffer count.
 * @returns {StorageBufferNode}
 */
const storage = ( value, type = null, count = 0 ) => new StorageBufferNode( value, type, count );

const _columns = /*@__PURE__*/ new WeakMap();

/**
 * Returns a column of the given buffer attribute, like a column of the matrices of an
 * instanced mesh. The column views the attribute itself, so both share the same GPU buffer.
 *
 * @private
 * @param {BufferAttribute} attribute - The buffer attribute.
 * @param {string} type - The type of the column (e.g. `'vec4'`).
 * @param {number} offset - The offset of the column.
 * @return {InterleavedBufferAttribute} The column.
 */
function getAttributeColumn( attribute, type, offset ) {

	let columns = _columns.get( attribute );

	if ( columns === undefined ) {

		columns = [];

		_columns.set( attribute, columns );

	}

	return columns[ offset ] || ( columns[ offset ] = new InterleavedBufferAttribute( attribute, getLengthFromType( type ), offset ) );

}

/**
 * A buffer attribute node that binds the attribute of the rendered object
 * referred to by a {@link ReferenceAttributeNode}.
 *
 * @private
 * @augments BufferAttributeNode
 */
class ReferenceBufferAttributeNode extends BufferAttributeNode {

	static get type() {

		return 'ReferenceBufferAttributeNode';

	}

	/**
	 * Constructs a new reference buffer attribute node.
	 *
	 * @param {ReferenceAttributeNode} referenceNode - The reference attribute node.
	 * @param {string} bufferType - The buffer type (e.g. `'vec3'`).
	 * @param {number} [bufferStride=0] - The buffer stride, when binding a column of the attribute.
	 * @param {number} [bufferOffset=0] - The buffer offset, when binding a column of the attribute.
	 */
	constructor( referenceNode, bufferType, bufferStride = 0, bufferOffset = 0 ) {

		super( null, bufferType, bufferStride, bufferOffset );

		/**
		 * The reference attribute node.
		 *
		 * @type {ReferenceAttributeNode}
		 */
		this.referenceNode = referenceNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isReferenceBufferAttributeNode = true;

	}

	/**
	 * Overwritten since the node has no attribute data of its own.
	 *
	 * @return {string} The hash.
	 */
	getHash() {

		return String( this.id );

	}

	/**
	 * Returns the referred attribute of the given object, or its column.
	 *
	 * @param {Object3D} object - The rendered object.
	 * @return {?(BufferAttribute|InterleavedBufferAttribute)} The buffer attribute.
	 */
	getObjectAttribute( object ) {

		const attribute = this.referenceNode.getAttribute( object );

		if ( ! attribute || this.bufferStride === 0 ) return attribute;

		return getAttributeColumn( attribute, this.bufferType, this.bufferOffset );

	}

	/**
	 * The attribute of the object the program is built for defines the layout.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setup( builder ) {

		this.attribute = this.getObjectAttribute( builder.object );

	}

}

/**
 * This node binds a buffer attribute that is a property of the rendered object, like the
 * matrices of an instanced mesh. Unlike {@link BufferAttributeNode}, the attribute isn't part
 * of the program: each object binds its own attribute, so objects can share a program.
 * ```js
 * const matrix = referenceAttribute( 'instanceMatrix', 'mat4' );
 * const color = referenceAttribute( 'instanceColor' );
 * ```
 * Matrices are bound as columns. Storage buffer attributes are bound as storage buffers.
 *
 * @augments Node
 */
class ReferenceAttributeNode extends Node {

	static get type() {

		return 'ReferenceAttributeNode';

	}

	/**
	 * Constructs a new reference attribute node.
	 *
	 * @param {string} property - The name of the property the node refers to.
	 * @param {?string} [nodeType=null] - The node type. Inferred from the attribute if not set.
	 * @param {?Object} [object=null] - The object the property belongs to. The rendered object if not set.
	 */
	constructor( property, nodeType = null, object = null ) {

		super( nodeType );

		/**
		 * The name of the property the node refers to.
		 *
		 * @type {string}
		 */
		this.property = property;

		/**
		 * The property name might have dots so nested properties can be referred.
		 * The hierarchy of the names is stored inside this array.
		 *
		 * @type {Array<string>}
		 */
		this.properties = property.split( '.' );

		/**
		 * The object the property belongs to. If `null`, the rendered object is used.
		 *
		 * @type {?Object}
		 * @default null
		 */
		this.object = object;

	}

	/**
	 * Returns the value of the referred property of the given object.
	 *
	 * @param {Object} object - The object.
	 * @return {?(BufferAttribute|InterleavedBufferAttribute)} The value of the referred property.
	 */
	getValueFromReference( object ) {

		let value = object;

		for ( const property of this.properties ) {

			if ( value === undefined || value === null ) break;

			value = value[ property ];

		}

		return value;

	}

	/**
	 * Returns the attribute the node binds when rendering the given object.
	 *
	 * @param {Object3D} object - The rendered object.
	 * @return {?(BufferAttribute|InterleavedBufferAttribute)} The buffer attribute.
	 */
	getAttribute( object ) {

		return this.getValueFromReference( this.object !== null ? this.object : object );

	}

	/**
	 * Returns the node type, inferred from the attribute if not set.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		if ( this.nodeType !== null ) return this.nodeType;

		const attribute = this.getAttribute( builder.object );

		return attribute ? builder.getTypeFromAttribute( attribute ) : 'float';

	}

	/**
	 * Sets up the attribute nodes.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node} The attribute node.
	 */
	setup( builder ) {

		const attribute = this.getAttribute( builder.object );

		if ( ! attribute ) throw new Error( `THREE.ReferenceAttributeNode: Attribute "${ this.property }" not found.` );

		const type = this.getNodeType( builder );

		if ( attribute.isStorageBufferAttribute === true || attribute.isStorageInstancedBufferAttribute === true ) {

			const index = attribute.isStorageInstancedBufferAttribute === true ? instanceIndex : vertexIndex;

			return storage( attribute, type, Math.max( attribute.count, 1 ) ).onObjectUpdate( ( { object } ) => this.getAttribute( object ) ).element( index );

		}

		if ( type === 'mat4' ) {

			return mat4( ...[ 0, 4, 8, 12 ].map( ( offset ) => new ReferenceBufferAttributeNode( this, 'vec4', 16, offset ) ) );

		} else if ( type === 'mat3' ) {

			return mat3( ...[ 0, 3, 6 ].map( ( offset ) => new ReferenceBufferAttributeNode( this, 'vec3', 9, offset ) ) );

		}

		return new ReferenceBufferAttributeNode( this, type );

	}

}

/**
 * TSL function for creating a reference attribute node.
 *
 * @tsl
 * @function
 * @param {string} name - The name of the property the node refers to.
 * @param {?string} [type=null] - The node type. Inferred from the attribute if not set.
 * @param {?Object} [object=null] - The object the property belongs to. The rendered object if not set.
 * @returns {ReferenceAttributeNode}
 */
const referenceAttribute = ( name, type = null, object = null ) => new ReferenceAttributeNode( name, type, object );

const _previousInstanceMatrices = /*@__PURE__*/ new WeakMap();
const _previousMatrices = /*@__PURE__*/ new WeakMap();

// The default limit of vertex buffers in WebGPU.
const MAX_VERTEX_BUFFERS = 8;

/**
 * Creates the appropriate node for instanced matrix transformations.
 * Depending on buffer limits and storage capability, returns either a storage, buffer, or instanced interleaved attribute node.
 *
 * @param {NodeBuilder} builder - The current node builder.
 * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - The matrix buffer attribute.
 * @returns {Node} The matrix node.
 */
function createInstanceMatrixNode( builder, instanceMatrix ) {

	let instanceMatrixNode;
	const matrixCount = Math.max( instanceMatrix.count, 1 );

	const isStorageMatrix = instanceMatrix.isStorageInstancedBufferAttribute === true;

	if ( isStorageMatrix ) {

		instanceMatrixNode = storage( instanceMatrix, 'mat4', matrixCount ).element( instanceIndex );

	} else {

		const uniformBufferSize = matrixCount * 16 * 4;

		if ( uniformBufferSize <= builder.getUniformBufferLimit() ) {

			instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', matrixCount ).element( instanceIndex );

		} else {

			// the columns view the matrices, so both share the same GPU buffer which is freed via instanceMatrix.dispose()

			const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;

			const instanceBuffers = [ 0, 4, 8, 12 ].map( ( offset ) => bufferFn( new InterleavedBufferAttribute( instanceMatrix, 4, offset ) ) );

			instanceMatrixNode = mat4( ...instanceBuffers );

		}

	}

	return instanceMatrixNode;

}

/**
 * Retrieves or initializes the previous frame instance matrix node for motion vectors.
 * Uses a WeakMap to cache previous frame instance matrices and their TSL nodes.
 *
 * @param {InstancedMesh} instancedMesh - The instanced mesh object.
 * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - The current matrix buffer attribute.
 * @param {NodeBuilder} builder - The current node builder.
 * @returns {Node} The previous frame instance matrix node.
 */
function getPreviousInstance( instancedMesh, instanceMatrix, builder ) {

	let data = _previousInstanceMatrices.get( instancedMesh );

	if ( data === undefined ) {

		const previousInstanceMatrix = instanceMatrix.clone();

		data = {
			previousInstanceMatrix,
			node: createInstanceMatrixNode( builder, previousInstanceMatrix )
		};

		_previousInstanceMatrices.set( instancedMesh, data );

		instancedMesh.addEventListener( 'dispose', onPreviousInstanceDispose );

	}

	return data.node;

}

/**
 * Frees the GPU buffer of the previous frame instance matrices when the mesh is disposed.
 * The matrices are kept since the node refers to them, rendering the mesh again re-creates the buffer.
 *
 * @param {Object} event - The dispose event.
 */
function onPreviousInstanceDispose( { target: object } ) {

	_previousInstanceMatrices.get( object ).previousInstanceMatrix.dispose();

}

/**
 * Returns the instance matrices of the previous frame of the given mesh, for motion vectors.
 *
 * @param {InstancedMesh} object - The instanced mesh.
 * @returns {InstancedBufferAttribute} The matrices of the previous frame.
 */
function getPreviousMatrix$1( object ) {

	const matrices = object.instanceMatrix;

	let previous = _previousMatrices.get( object );

	if ( previous === undefined || previous.count !== matrices.count ) {

		if ( previous === undefined ) {

			object.addEventListener( 'dispose', onPreviousMatrixDispose );

		} else {

			previous.dispose();

		}

		previous = matrices.clone();

		_previousMatrices.set( object, previous );

	}

	return previous;

}

/**
 * Disposes the instance matrices of the previous frame when the mesh is disposed.
 *
 * @param {Object} event - The dispose event.
 */
function onPreviousMatrixDispose( { target: object } ) {

	object.removeEventListener( 'dispose', onPreviousMatrixDispose );

	_previousMatrices.get( object ).dispose();
	_previousMatrices.delete( object );

}

/**
 * Binds the instance matrices of the previous frame of the rendered mesh.
 *
 * @private
 * @augments ReferenceAttributeNode
 */
class PreviousInstanceMatrixNode extends ReferenceAttributeNode {

	static get type() {

		return 'PreviousInstanceMatrixNode';

	}

	/**
	 * Constructs a new previous instance matrix node.
	 */
	constructor() {

		super( 'previousInstanceMatrix', 'mat4' );

	}

	/**
	 * Returns the instance matrices of the previous frame of the given mesh.
	 *
	 * @param {InstancedMesh} object - The instanced mesh.
	 * @return {InstancedBufferAttribute} The matrices of the previous frame.
	 */
	getValueFromReference( object ) {

		return getPreviousMatrix$1( object );

	}

}

/**
 * Returns `true` if the build can bind the instance data of the rendered mesh, so it can
 * be shared between instanced meshes.
 *
 * @param {InstancedMesh} object - The instanced mesh.
 * @param {NodeBuilder} builder - The current node builder.
 * @returns {boolean} Whether the build can bind the instance data of the rendered mesh.
 */
function canReferenceInstanceData( object, builder ) {

	// only render objects bind attributes per object

	if ( builder.renderer.isRenderer !== true ) return false;

	// the instance data take up to three vertex buffers next to the ones of the geometry

	const instanceBuffers = 1 + ( object.instanceColor !== null ? 1 : 0 ) + ( builder.needsPreviousData() ? 1 : 0 );

	return Object.keys( object.geometry.attributes ).length + instanceBuffers <= MAX_VERTEX_BUFFERS;

}

/**
 * Transforms the position, the previous position and the normal with the given instance matrices.
 *
 * @param {NodeBuilder} builder - The current node builder.
 * @param {Node<mat4>} instanceMatrixNode - The instance matrix.
 * @param {?Node<mat4>} previousMatrixNode - The instance matrix of the previous frame, if motion vectors are needed.
 */
function transformInstance( builder, instanceMatrixNode, previousMatrixNode ) {

	// POSITION

	const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;
	positionLocal.assign( instancePosition );

	if ( previousMatrixNode !== null ) {

		positionPrevious.assign( previousMatrixNode.mul( positionPrevious ).xyz );

	}

	// NORMAL

	if ( builder.hasGeometryAttribute( 'normal' ) ) {

		const instanceNormal = transformNormal( normalLocal, instanceMatrixNode );
		normalLocal.assign( instanceNormal );

	}

}

/**
 * TSL object representing a varying property for the instanced color vector.
 *
 * @type {VaryingNode<vec3>}
 */
const instanceColor = /*@__PURE__*/ varyingProperty( 'vec3', 'vInstanceColor' );

/**
 * TSL function representing the standard instancing vertex shader setup.
 * Transforms positionLocal and normalLocal, and assigns varying color in-place.
 *
 * @tsl
 * @function
 * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} matrices - The instanced transformation matrices.
 * @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} [colors=null] - The optional instanced colors.
 */
const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => {

	const isStorageColor = colors && colors.isStorageInstancedBufferAttribute === true;

	const instanceMatrixNode = createInstanceMatrixNode( builder, matrices );

	let instanceColorNode = null;

	if ( colors ) {

		if ( isStorageColor ) {

			instanceColorNode = storage( colors, 'vec3', Math.max( colors.count, 1 ) ).element( instanceIndex );

		} else {

			// bound directly, so the GPU buffer is freed via instanceColor.dispose()

			const bufferFn = colors.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;

			instanceColorNode = vec3( bufferFn( colors ) );

		}

	}

	let previousInstanceMatrixNode = null;

	if ( builder.needsPreviousData() ) {

		const instancedMesh = builder.object;

		OnAfterObjectUpdate( ( { object } ) => {

			const { previousInstanceMatrix } = _previousInstanceMatrices.get( object );

			previousInstanceMatrix.array.set( matrices.array );
			previousInstanceMatrix.version = matrices.version;

		} );

		previousInstanceMatrixNode = getPreviousInstance( instancedMesh, matrices, builder );

	}

	transformInstance( builder, instanceMatrixNode, previousInstanceMatrixNode );

	// COLOR

	if ( instanceColorNode !== null ) {

		instanceColor.assign( instanceColorNode );

	}

}, 'void' );

/**
 * TSL wrapper for applying instanced mesh rendering setup.
 *
 * The instance data of the rendered mesh are bound per object via {@link ReferenceAttributeNode},
 * so instanced meshes share builds.
 *
 * @tsl
 * @function
 * @param {InstancedMesh} instancedMesh - The instanced mesh.
 */
const instancedMesh = /*@__PURE__*/ Fn( ( [ instancedMesh ], builder ) => {

	if ( canReferenceInstanceData( instancedMesh, builder ) ) {

		let previousInstanceMatrixNode = null;

		if ( builder.needsPreviousData() ) {

			OnAfterObjectUpdate( ( { object } ) => {

				const previous = getPreviousMatrix$1( object );

				previous.array.set( object.instanceMatrix.array );
				previous.version = object.instanceMatrix.version;

			} );

			previousInstanceMatrixNode = new PreviousInstanceMatrixNode();

		}

		transformInstance( builder, referenceAttribute( 'instanceMatrix', 'mat4' ), previousInstanceMatrixNode );

		if ( instancedMesh.instanceColor !== null ) {

			instanceColor.assign( referenceAttribute( 'instanceColor', 'vec3' ) );

		}

	} else {

		builder.objectSpecific = true;

		instance( instancedMesh.instanceMatrix, instancedMesh.instanceColor );

	}

}, 'void' );

/**
 * TSL function that retrieves the batching color for a given instance ID from a colors texture.
 *
 * @param {Node<texture>} colorsTexture - The colors texture.
 * @param {Node<int>} id - The instance or batch ID.
 * @returns {Node<vec4>} The retrieved color.
 */
const getBatchingColor = /*@__PURE__*/ Fn( ( [ colorsTexture, id ] ) => {

	const size = int( textureSize( textureLoad( colorsTexture ), 0 ).x ).toConst();
	const j = int( id );
	const x = j.mod( size ).toConst();
	const y = j.div( size ).toConst();
	return textureLoad( colorsTexture, ivec2( x, y ) );

} );

/**
 * TSL function that retrieves the indirect index for a given batch ID.
 *
 * @param {BatchedMesh} batchMesh - The batched mesh.
 * @param {Node<int>} id - The draw or instance ID.
 * @returns {Node<uint>} The indirect index.
 */
const getIndirectIndex = /*@__PURE__*/ Fn( ( [ indirectTexture, id ] ) => {

	const size = int( textureSize( textureLoad( indirectTexture ), 0 ).x ).toConst();
	const x = int( id ).mod( size ).toConst();
	const y = int( id ).div( size ).toConst();
	return textureLoad( indirectTexture, ivec2( x, y ) ).x;

} );

/**
 * Creates the node that reads a batching matrix from the given matrices texture.
 *
 * @param {Texture} matricesTexture - The matrices texture.
 * @param {Node<uint>} id - The indirect instance ID.
 * @returns {Node<mat4>} The matrix node.
 */
function createBatchingMatrixNode( matricesTexture, id ) {

	const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst();
	const j = float( id ).mul( 4 ).toInt().toConst();

	const x = j.mod( size ).toConst();
	const y = j.div( size ).toConst();

	return mat4(
		textureLoad( matricesTexture, ivec2( x, y ) ),
		textureLoad( matricesTexture, ivec2( x.add( 1 ), y ) ),
		textureLoad( matricesTexture, ivec2( x.add( 2 ), y ) ),
		textureLoad( matricesTexture, ivec2( x.add( 3 ), y ) )
	);

}

/**
 * Returns the batching matrices texture of the previous frame for motion vectors.
 * It is created on first use and again after the batched mesh released it.
 *
 * @param {BatchedMesh} batchMesh - The batched mesh.
 * @returns {DataTexture} The previous frame matrices texture.
 */
function getPreviousMatricesTexture( batchMesh ) {

	let previousMatricesTexture = batchMesh._previousMatricesTexture;

	if ( previousMatricesTexture === null ) {

		const { image, format, type, version } = batchMesh._matricesTexture;

		previousMatricesTexture = new DataTexture( image.data.slice(), image.width, image.height, format, type );
		previousMatricesTexture.version = version;

		batchMesh._previousMatricesTexture = previousMatricesTexture;

	}

	return previousMatricesTexture;

}

/**
 * TSL object representing a varying property for the batching color vector.
 *
 * @type {VaryingNode<vec4>}
 */
const batchColor = /*@__PURE__*/ varyingProperty( 'vec4', 'vBatchColor' );

/**
 * TSL object representing a varying property for the batch indirect index (instance ID).
 *
 * @type {VaryingNode<uint>}
 */
const batchIndirectIndex = /*@__PURE__*/ varyingProperty( 'uint', 'vBatchIndirectId' );

/**
 * TSL function representing the vertex shader batching setup.
 * Applies the batch transformation matrix to positionLocal, normalLocal, and tangentLocal.
 * Also assigns the batch color if a color texture is present.
 *
 * @tsl
 * @function
 * @param {BatchedMesh} batchMesh - The batched mesh.
 */
const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => {

	const batchingIdNode = builder.getDrawIndex() === null ? instanceIndex : drawIndex;

	const indirectId = getIndirectIndex( batchMesh._indirectTexture, int( batchingIdNode ) );

	batchIndirectIndex.assign( indirectId );

	const batchingMatrix = createBatchingMatrixNode( batchMesh._matricesTexture, indirectId );

	const colorsTexture = batchMesh._colorsTexture;

	if ( colorsTexture !== null ) {

		const color = getBatchingColor( colorsTexture, indirectId );

		batchColor.assign( color );

	}

	const bm = mat3( batchingMatrix );

	positionLocal.assign( batchingMatrix.mul( positionLocal ) );

	if ( builder.needsPreviousData() ) {

		OnAfterObjectUpdate( ( { object } ) => {

			const previousMatricesTexture = getPreviousMatricesTexture( object );
			const matricesTexture = object._matricesTexture;

			if ( previousMatricesTexture.version !== matricesTexture.version ) {

				previousMatricesTexture.image.data.set( matricesTexture.image.data );
				previousMatricesTexture.version = matricesTexture.version;

			}

		} );

		const previousBatchingMatrixNode = createBatchingMatrixNode( getPreviousMatricesTexture( batchMesh ), indirectId );
		positionPrevious.assign( previousBatchingMatrixNode.mul( positionPrevious ).xyz );

	}

	const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) );

	const batchingNormal = bm.mul( transformedNormal ).xyz;

	normalLocal.assign( batchingNormal );

	if ( builder.hasGeometryAttribute( 'tangent' ) ) {

		tangentLocal.mulAssign( bm );

	}

	const drawFormat = builder.renderer.backend.isWebGPUBackend === true ? 'merged' : 'multiDraw';

	OnObjectUpdate( ( { object, camera, material } ) => {

		object._updateDrawParameters( camera, object.geometry, material, drawFormat );

	} );

}, 'void' );

const _skeletonsUpdated = /*@__PURE__*/ new WeakMap();

/**
 * Creates an accessor for bone matrices stored in a bone texture.
 *
 * @param {TextureNode} boneTexture - The bone texture node.
 * @returns {Object} An accessor with the same `element()` interface as a buffer node.
 */
function getBoneTextureMatrices( boneTexture ) {

	return {
		element: ( i ) => {

			const size = int( textureSize( boneTexture ).x ).toConst();
			const j = int( i ).mul( 4 ).toConst();
			const y = j.div( size ).toConst();
			const x = j.sub( y.mul( size ) ).toConst();

			return mat4(
				boneTexture.load( ivec2( x, y ) ),
				boneTexture.load( ivec2( x.add( 1 ), y ) ),
				boneTexture.load( ivec2( x.add( 2 ), y ) ),
				boneTexture.load( ivec2( x.add( 3 ), y ) )
			);

		}
	};

}

/**
 * Creates the bone matrices node. Skeletons that fit within the uniform buffer limit
 * use a uniform buffer, larger skeletons fall back to a bone texture.
 *
 * @param {NodeBuilder} builder - The current node builder.
 * @param {Skeleton} skeleton - The skeleton.
 * @returns {Object} The bone matrices node.
 */
function getBoneMatricesNode( builder, skeleton ) {

	let node;

	const uniformBufferSize = skeleton.bones.length * 16 * 4;

	if ( uniformBufferSize <= builder.getUniformBufferLimit() ) {

		node = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skeleton.bones.length );

	} else {

		if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture();

		const boneTexture = texture( skeleton.boneTexture );

		OnObjectUpdate( ( { object } ) => {

			const skeleton = object.skeleton;

			if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture();

			boneTexture.value = skeleton.boneTexture;

		} );

		node = getBoneTextureMatrices( boneTexture );

	}

	return node;

}

/**
 * Computes the skinned position by applying bone matrices based on weights.
 *
 * @param {Node} boneMatrices - The bone matrices buffer or storage node.
 * @param {Node<vec3>} position - The vertex position to transform.
 * @param {Node<mat4>} bindMatrix - The bind matrix node.
 * @param {Node<mat4>} bindMatrixInverse - The inverse bind matrix node.
 * @param {Node<uvec4>} skinIndex - The skin index attribute.
 * @param {Node<vec4>} skinWeight - The skin weight attribute.
 * @returns {Node<vec3>} The skinned position.
 */
function getSkinnedPosition( boneMatrices, position, bindMatrix, bindMatrixInverse, skinIndex, skinWeight ) {

	const boneMatX = boneMatrices.element( skinIndex.x );
	const boneMatY = boneMatrices.element( skinIndex.y );
	const boneMatZ = boneMatrices.element( skinIndex.z );
	const boneMatW = boneMatrices.element( skinIndex.w );

	// POSITION

	const skinVertex = bindMatrix.mul( position );

	const skinned = add(
		boneMatX.mul( skinWeight.x ).mul( skinVertex ),
		boneMatY.mul( skinWeight.y ).mul( skinVertex ),
		boneMatZ.mul( skinWeight.z ).mul( skinVertex ),
		boneMatW.mul( skinWeight.w ).mul( skinVertex )
	);

	return bindMatrixInverse.mul( skinned ).xyz;

}

/**
 * Computes the skinned normal and tangent vectors by applying bone matrices based on weights.
 *
 * @param {Node} boneMatrices - The bone matrices buffer or storage node.
 * @param {Node<vec3>} normal - The normal vector in local space.
 * @param {Node<vec3>} tangent - The tangent vector in local space.
 * @param {Node<mat4>} bindMatrix - The bind matrix node.
 * @param {Node<mat4>} bindMatrixInverse - The inverse bind matrix node.
 * @param {Node<uvec4>} skinIndex - The skin index attribute.
 * @param {Node<vec4>} skinWeight - The skin weight attribute.
 * @returns {{skinNormal: Node<vec3>, skinTangent: Node<vec3>}} The skinned normal and tangent.
 */
function getSkinnedNormalAndTangent( boneMatrices, normal, tangent, bindMatrix, bindMatrixInverse, skinIndex, skinWeight ) {

	const boneMatX = boneMatrices.element( skinIndex.x );
	const boneMatY = boneMatrices.element( skinIndex.y );
	const boneMatZ = boneMatrices.element( skinIndex.z );
	const boneMatW = boneMatrices.element( skinIndex.w );

	// NORMAL and TANGENT

	let skinMatrix = add(
		skinWeight.x.mul( boneMatX ),
		skinWeight.y.mul( boneMatY ),
		skinWeight.z.mul( boneMatZ ),
		skinWeight.w.mul( boneMatW )
	);

	skinMatrix = bindMatrixInverse.mul( skinMatrix ).mul( bindMatrix );

	const skinMatrix3 = mat3( skinMatrix );

	const skinNormal = skinMatrix3.mul( normal );
	const skinTangent = skinMatrix3.mul( tangent );

	return { skinNormal, skinTangent };

}

/**
 * Creates the bone data of the previous frame if they do not exist yet.
 *
 * @param {Skeleton} skeleton - The skeleton.
 */
function setupPreviousBoneData( skeleton ) {

	if ( skeleton.previousBoneMatrices === null ) {

		skeleton.update();

		skeleton.previousBoneMatrices = new Float32Array( skeleton.boneMatrices );

	}

	if ( skeleton.previousBoneTexture === null && skeleton.boneTexture !== null ) {

		const { width, height } = skeleton.boneTexture.image;

		skeleton.previousBoneTexture = new DataTexture( skeleton.previousBoneMatrices, width, height, RGBAFormat, FloatType );
		skeleton.previousBoneTexture.needsUpdate = true;

	}

}

/**
 * Creates the bone matrices node of the previous frame.
 *
 * @param {NodeBuilder} builder - The current node builder.
 * @param {Skeleton} skeleton - The skeleton.
 * @returns {Object} The previous bone matrices node.
 */
function getPreviousBoneMatricesNode( builder, skeleton ) {

	let node;

	setupPreviousBoneData( skeleton );

	const uniformBufferSize = skeleton.bones.length * 16 * 4;

	if ( uniformBufferSize <= builder.getUniformBufferLimit() ) {

		node = referenceBuffer( 'skeleton.previousBoneMatrices', 'mat4', skeleton.bones.length );

	} else {

		const previousBoneTexture = texture( skeleton.previousBoneTexture );

		OnObjectUpdate( ( { object } ) => {

			previousBoneTexture.value = object.skeleton.previousBoneTexture;

		} );

		node = getBoneTextureMatrices( previousBoneTexture );

	}

	return node;

}

/**
 * TSL function representing the standard skeletal animation vertex shader setup.
 * Transforms positionLocal, normalLocal, and tangentLocal in-place.
 *
 * @tsl
 * @function
 * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
 */
const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => {

	const skinIndexNode = attribute( 'skinIndex', 'uvec4' );
	const skinWeightNode = attribute( 'skinWeight', 'vec4' );
	const bindMatrixNode = reference( 'bindMatrix', 'mat4' );
	const bindMatrixInverseNode = reference( 'bindMatrixInverse', 'mat4' );
	const boneMatricesNode = getBoneMatricesNode( builder, skinnedMesh.skeleton );
	const needsPreviousData = builder.needsPreviousData();

	OnObjectUpdate( ( { object, frameId } ) => {

		const skeleton = object.skeleton;

		if ( needsPreviousData ) setupPreviousBoneData( skeleton );

		if ( _skeletonsUpdated.get( skeleton ) !== frameId ) {

			_skeletonsUpdated.set( skeleton, frameId );

			if ( skeleton.previousBoneMatrices !== null ) {

				skeleton.previousBoneMatrices.set( skeleton.boneMatrices );

				if ( skeleton.previousBoneTexture !== null ) {

					skeleton.previousBoneTexture.needsUpdate = true;

				}

			}

			skeleton.update();

		}

	} );

	if ( needsPreviousData ) {

		const previousBoneMatricesNode = getPreviousBoneMatricesNode( builder, skinnedMesh.skeleton );
		const previousSkinnedPosition = getSkinnedPosition( previousBoneMatricesNode, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );

		positionPrevious.assign( previousSkinnedPosition );

	}

	const skinPosition = getSkinnedPosition( boneMatricesNode, positionLocal, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );
	positionLocal.assign( skinPosition );

	if ( builder.hasGeometryAttribute( 'normal' ) ) {

		const { skinNormal, skinTangent } = getSkinnedNormalAndTangent( boneMatricesNode, normalLocal, tangentLocal, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );

		normalLocal.assign( skinNormal );

		if ( builder.hasGeometryAttribute( 'tangent' ) ) {

			tangentLocal.assign( skinTangent );

		}

	}

}, 'void' );

/**
 * TSL function that computes skeletal animation for custom compute passes.
 *
 * @tsl
 * @function
 * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
 * @param {Node<vec3>} [toPosition=null] - The target position node to assign.
 * @returns {Node<vec3>} The computed skinned position node.
 */
const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = null ], builder ) => {

	const positionNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'position' ).array, 3 ), 'vec3' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
	const skinIndexNode = storage( new InstancedBufferAttribute( new Uint32Array( skinnedMesh.geometry.getAttribute( 'skinIndex' ).array ), 4 ), 'uvec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
	const skinWeightNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'skinWeight' ).array, 4 ), 'vec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
	const bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' );
	const bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' );
	const boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length );

	const skeleton = skinnedMesh.skeleton;

	OnObjectUpdate( ( { frameId } ) => {

		if ( _skeletonsUpdated.get( skeleton ) !== frameId ) {

			_skeletonsUpdated.set( skeleton, frameId );

			if ( skeleton.previousBoneMatrices !== null ) {

				skeleton.previousBoneMatrices.set( skeleton.boneMatrices );

				if ( skeleton.previousBoneTexture !== null ) {

					skeleton.previousBoneTexture.needsUpdate = true;

				}

			}

			skeleton.update();

		}

	} );

	if ( builder.needsPreviousData() ) {

		setupPreviousBoneData( skeleton );

		const previousBoneMatricesNode = buffer( skeleton.previousBoneMatrices, 'mat4', skeleton.bones.length );
		const previousSkinnedPosition = getSkinnedPosition( previousBoneMatricesNode, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );

		positionPrevious.assign( previousSkinnedPosition );

	}

	const skinPosition = getSkinnedPosition( boneMatricesNode, positionNode, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );

	if ( toPosition !== null ) {

		toPosition.assign( skinPosition );

	}

	if ( builder.hasGeometryAttribute( 'normal' ) ) {

		const { skinNormal, skinTangent } = getSkinnedNormalAndTangent( boneMatricesNode, normalLocal, tangentLocal, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );

		normalLocal.assign( skinNormal );

		if ( builder.hasGeometryAttribute( 'tangent' ) ) {

			tangentLocal.assign( skinTangent );

		}

	}

	return skinPosition;

} );

const _morphTextures = /*@__PURE__*/ new WeakMap();
const _morphVec4 = /*@__PURE__*/ new Vector4();
const _morphInfluencesData = /*@__PURE__*/ new WeakMap();

/**
 * TSL function that retrieves and scales the morphed attribute (position, normal or color) texel value.
 *
 * @param {Object} params - The parameter object.
 * @param {Node<texture>} params.bufferMap - The morph target data array texture.
 * @param {Node<float>} params.influence - The target's animation influence weight.
 * @param {number} params.stride - The vertex data stride (e.g. 1 or 2).
 * @param {Node<int>} params.width - The texture width limit.
 * @param {Node<int>} params.depth - The target layer index (morph target index).
 * @param {Node<int>} params.offset - The texture offset (e.g. 0 for position, 1 for normal, 2 for color).
 * @returns {Node<vec4>} The scaled morph target value.
 */
const getMorph = /*@__PURE__*/ Fn( ( { bufferMap, influence, stride, width, depth, offset } ) => {

	const texelIndex = int( vertexIndex ).mul( stride ).add( offset );

	const y = texelIndex.div( width );
	const x = texelIndex.sub( y.mul( width ) );

	const bufferAttrib = textureLoad( bufferMap, ivec2( x, y ) ).depth( depth );

	return bufferAttrib.mul( influence );

} );

/**
 * The morph target base influence. It is `1` when `morphTargetsRelative`
 * is `true`, otherwise `1 - sum( influences )`.
 *
 * @type {UniformNode<float>}
 */
const morphBase = /*@__PURE__*/ uniform( 1 ).onObjectUpdate( ( { object } ) => {

	return object.geometry.morphTargetsRelative ? 1 : 1 - object.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 );

} );

/**
 * The sum of the weighted color morph targets.
 *
 * @type {PropertyNode<vec4>}
 */
const morphColorSum = /*@__PURE__*/ property( 'vec4', 'morphColorSum' );

/**
 * TSL function that applies the color morph targets to the given color.
 * The result is computed in the vertex stage.
 *
 * @tsl
 * @function
 * @param {Node<vec4>} color - The color to morph. Must be available in the vertex stage.
 * @returns {Node<vec4>} The morphed color.
 */
const morphColor = /*@__PURE__*/ Fn( ( [ color ], { geometry } ) => {

	if ( geometry.morphAttributes.color[ 0 ].itemSize === 4 ) {

		return varying( color.mul( morphBase ).add( morphColorSum ) );

	}

	return varying( vec4( color.rgb.mul( morphBase ).add( morphColorSum.rgb ), color.a ) );

} );

/**
 * Resolves or creates a compiled DataArrayTexture containing encoded vertex morph targets data for WebGL2/WebGPU.
 *
 * @param {BufferGeometry} geometry - The geometry to parse.
 * @returns {Object} The resolved morph targets texture data mapping entry.
 */
function getEntry( geometry ) {

	const hasMorphPosition = geometry.morphAttributes.position !== undefined;
	const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
	const hasMorphColors = geometry.morphAttributes.color !== undefined;

	// instead of using attributes, the WebGL 2 code path encodes morph targets
	// into an array of data textures. Each layer represents a single morph target.

	const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
	const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;

	let entry = _morphTextures.get( geometry );

	if ( entry === undefined || entry.count !== morphTargetsCount ) {

		if ( entry !== undefined ) entry.texture.dispose();

		const morphTargets = geometry.morphAttributes.position || [];
		const morphNormals = geometry.morphAttributes.normal || [];
		const morphColors = geometry.morphAttributes.color || [];

		let vertexDataCount = 0;

		if ( hasMorphPosition === true ) vertexDataCount = 1;
		if ( hasMorphNormals === true ) vertexDataCount = 2;
		if ( hasMorphColors === true ) vertexDataCount = 3;

		let width = geometry.attributes.position.count * vertexDataCount;
		let height = 1;

		const maxTextureSize = 4096; // @TODO: Use 'capabilities.maxTextureSize'

		if ( width > maxTextureSize ) {

			height = Math.ceil( width / maxTextureSize );
			width = maxTextureSize;

		}

		const buffer = new Float32Array( width * height * 4 * morphTargetsCount );

		const bufferTexture = new DataArrayTexture( buffer, width, height, morphTargetsCount );
		bufferTexture.type = FloatType;
		bufferTexture.needsUpdate = true;

		// fill buffer

		const vertexDataStride = vertexDataCount * 4;

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

			const morphTarget = morphTargets[ i ];
			const morphNormal = morphNormals[ i ];
			const morphColor = morphColors[ i ];

			const offset = width * height * 4 * i;

			for ( let j = 0; j < morphTarget.count; j ++ ) {

				const stride = j * vertexDataStride;

				if ( hasMorphPosition === true ) {

					_morphVec4.fromBufferAttribute( morphTarget, j );

					buffer[ offset + stride + 0 ] = _morphVec4.x;
					buffer[ offset + stride + 1 ] = _morphVec4.y;
					buffer[ offset + stride + 2 ] = _morphVec4.z;
					buffer[ offset + stride + 3 ] = 0;

				}

				if ( hasMorphNormals === true ) {

					_morphVec4.fromBufferAttribute( morphNormal, j );

					buffer[ offset + stride + 4 ] = _morphVec4.x;
					buffer[ offset + stride + 5 ] = _morphVec4.y;
					buffer[ offset + stride + 6 ] = _morphVec4.z;
					buffer[ offset + stride + 7 ] = 0;

				}

				if ( hasMorphColors === true ) {

					_morphVec4.fromBufferAttribute( morphColor, j );

					buffer[ offset + stride + 8 ] = _morphVec4.x;
					buffer[ offset + stride + 9 ] = _morphVec4.y;
					buffer[ offset + stride + 10 ] = _morphVec4.z;
					buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? _morphVec4.w : 1;

				}

			}

		}

		entry = {
			count: morphTargetsCount,
			texture: bufferTexture,
			stride: vertexDataCount,
			size: new Vector2( width, height )
		};

		_morphTextures.set( geometry, entry );

		function disposeTexture() {

			bufferTexture.dispose();

			_morphTextures.delete( geometry );

			geometry.removeEventListener( 'dispose', disposeTexture );

		}

		geometry.addEventListener( 'dispose', disposeTexture );

	}

	return entry;

}

/**
 * TSL function representing the vertex shader morph targets blend setup.
 * Dynamically computes morph targets weights and updates positionLocal and normalLocal in-place.
 *
 * @tsl
 * @function
 * @param {Mesh} mesh - The mesh.
 */
const morphReference = /*@__PURE__*/ Fn( ( [ mesh ], builder ) => {

	const { geometry } = mesh;

	const hasMorphPosition = geometry.morphAttributes.position !== undefined;
	const hasMorphNormals = geometry.hasAttribute( 'normal' ) && geometry.morphAttributes.normal !== undefined;
	const hasMorphColors = builder.material.vertexColors === true && geometry.morphAttributes.color !== undefined;
	const hasColorAlpha = hasMorphColors === true && geometry.morphAttributes.color[ 0 ].itemSize === 4;

	const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
	const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;

	if ( morphTargetsCount === 0 ) return;

	if ( mesh.morphTexture ) builder.objectSpecific = true;

	// Init

	let morphInfluenceData = _morphInfluencesData.get( mesh );

	if ( morphInfluenceData === undefined || morphInfluenceData.count !== morphTargetsCount ) {

		morphInfluenceData = {
			influences: mesh.morphTargetInfluences ? uniformArray( mesh.morphTargetInfluences, 'float' ) : null,
			count: morphTargetsCount
		};

		_morphInfluencesData.set( mesh, morphInfluenceData );

	}

	const { influences } = morphInfluenceData;

	// Shader

	const { texture: bufferMap, stride, size } = getEntry( geometry );

	if ( hasMorphPosition === true ) positionLocal.mulAssign( morphBase );
	if ( hasMorphNormals === true ) normalLocal.mulAssign( morphBase );
	if ( hasMorphColors === true ) morphColorSum.assign( 0 );

	const width = int( size.width );

	Loop( morphTargetsCount, ( { i } ) => {

		const influence = float( 0 ).toVar();

		if ( mesh.count > 1 && ( mesh.morphTexture !== null && mesh.morphTexture !== undefined ) ) {

			influence.assign( textureLoad( mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r );

		} else {

			influence.assign( influences.element( i ).toVar() );

		}

		If( influence.notEqual( 0 ), () => {

			if ( hasMorphPosition === true ) {

				positionLocal.addAssign( getMorph( {
					bufferMap,
					influence,
					stride,
					width,
					depth: i,
					offset: int( 0 )
				} ).xyz );

			}

			if ( hasMorphNormals === true ) {

				normalLocal.addAssign( getMorph( {
					bufferMap,
					influence,
					stride,
					width,
					depth: i,
					offset: int( 1 )
				} ).xyz );

			}

			if ( hasMorphColors === true ) {

				const morph = getMorph( {
					bufferMap,
					influence,
					stride,
					width,
					depth: i,
					offset: int( 2 )
				} );

				if ( hasColorAlpha === true ) {

					morphColorSum.addAssign( morph );

				} else {

					morphColorSum.rgb.addAssign( morph.rgb );

				}

			}

		} );

	} );

	// Update

	OnObjectUpdate( ( { object } ) => {

		const { influences } = morphInfluenceData;

		if ( influences ) {

			influences.array = object.morphTargetInfluences;
			influences.update();

		}

	} );

}, 'void' );

/**
 * Base class for lighting nodes.
 *
 * @augments Node
 */
class LightingNode extends Node {

	static get type() {

		return 'LightingNode';

	}

	/**
	 * Constructs a new lighting node.
	 */
	constructor() {

		super( 'vec3' );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isLightingNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

}

/**
 * A generic class that can be used by nodes which contribute
 * ambient occlusion to the scene. E.g. an ambient occlusion map
 * node can be used as input for this module. Used in {@link NodeMaterial}.
 *
 * @augments LightingNode
 */
class AONode extends LightingNode {

	static get type() {

		return 'AONode';

	}

	/**
	 * Constructs a new AO node.
	 *
	 * @param {?Node<float>} [aoNode=null] - The ambient occlusion node.
	 */
	constructor( aoNode = null ) {

		super();

		/**
		 * The ambient occlusion node.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.aoNode = aoNode;

	}

	setup( builder ) {

		builder.context.ambientOcclusion.mulAssign( this.aoNode );

	}

}

/**
 * `LightingContextNode` represents an extension of the {@link ContextNode} module
 * by adding lighting specific context data. It represents the runtime context of
 * {@link LightsNode}.
 *
 * @augments ContextNode
 */
class LightingContextNode extends ContextNode {

	static get type() {

		return 'LightingContextNode';

	}

	/**
	 * Constructs a new lighting context node.
	 *
	 * @param {LightsNode} lightsNode - The lights node.
	 * @param {?LightingModel} [lightingModel=null] - The current lighting model.
	 * @param {?Array<LightingNode>} materialLightings - The material lightings nodes.
	 * @param {?Node<vec3>} [backdropNode=null] - A backdrop node.
	 * @param {?Node<float>} [backdropAlphaNode=null] - A backdrop alpha node.
	 */
	constructor( lightsNode, lightingModel = null, materialLightings = [], backdropNode = null, backdropAlphaNode = null ) {

		super( lightsNode );

		/**
		 * The current lighting model.
		 *
		 * @type {?LightingModel}
		 * @default null
		 */
		this.lightingModel = lightingModel;

		/**
		 * @type {?Array<LightingNode>}
		 * @default []
		 */
		this.materialLightings = materialLightings;

		/**
		 * A backdrop node.
		 *
		 * @type {?Node<vec3>}
		 * @default null
		 */
		this.backdropNode = backdropNode;

		/**
		 * A backdrop alpha node.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.backdropAlphaNode = backdropAlphaNode;

		this._value = null;

	}

	/**
	 * Returns a lighting context object.
	 *
	 * @return {{
	 * radiance: Node<vec3>,
	 * irradiance: Node<vec3>,
	 * iblIrradiance: Node<vec3>,
	 * ambientOcclusion: Node<float>,
	 * reflectedLight: {directDiffuse: Node<vec3>, directSpecular: Node<vec3>, indirectDiffuse: Node<vec3>, indirectSpecular: Node<vec3>},
	 * backdrop: Node<vec3>,
	 * backdropAlpha: Node<float>
	 * }} The lighting context object.
	 */
	getContext() {

		const { materialLightings, backdropNode, backdropAlphaNode } = this;

		const directDiffuse = vec3().toVar( 'directDiffuse' ),
			directSpecular = vec3().toVar( 'directSpecular' ),
			indirectDiffuse = vec3().toVar( 'indirectDiffuse' ),
			indirectSpecular = vec3().toVar( 'indirectSpecular' );

		const reflectedLight = {
			directDiffuse,
			directSpecular,
			indirectDiffuse,
			indirectSpecular
		};

		const context = {
			radiance: vec3().toVar( 'radiance' ),
			irradiance: vec3().toVar( 'irradiance' ),
			iblIrradiance: vec3().toVar( 'iblIrradiance' ),
			ambientOcclusion: float( 1 ).toVar( 'ambientOcclusion' ),
			reflectedLight,
			materialLightings,
			backdrop: backdropNode,
			backdropAlpha: backdropAlphaNode
		};

		return context;

	}

	setup( builder ) {

		this.value = this._value || ( this._value = this.getContext() );
		this.value.lightingModel = this.lightingModel || builder.context.lightingModel;

		return super.setup( builder );

	}

}

const lightingContext = /*@__PURE__*/ nodeProxy( LightingContextNode );

/**
 * A generic class that can be used by nodes which contribute
 * irradiance to the scene. E.g. a light map node can be used
 * as input for this module. Used in {@link NodeMaterial}.
 *
 * @augments LightingNode
 */
class IrradianceNode extends LightingNode {

	static get type() {

		return 'IrradianceNode';

	}

	/**
	 * Constructs a new irradiance node.
	 *
	 * @param {Node<vec3>} node - A node contributing irradiance.
	 */
	constructor( node ) {

		super();

		/**
		 * A node contributing irradiance.
		 *
		 * @type {Node<vec3>}
		 */
		this.node = node;

	}

	setup( builder ) {

		builder.context.irradiance.addAssign( this.node );

	}

}

/**
 * This node is used in {@link NodeMaterial} to setup the clipping
 * which can happen hardware-accelerated (if supported) and optionally
 * use alpha-to-coverage for anti-aliasing clipped edges.
 *
 * @augments Node
 */
class ClippingNode extends Node {

	static get type() {

		return 'ClippingNode';

	}

	/**
	 * Constructs a new clipping node.
	 *
	 * @param {('default'|'hardware'|'alphaToCoverage')} [scope='default'] - The node's scope. Similar to other nodes,
	 * the selected scope influences the behavior of the node and what type of code is generated.
	 */
	constructor( scope = ClippingNode.DEFAULT ) {

		super();

		/**
		 * The node's scope. Similar to other nodes, the selected scope influences
		 * the behavior of the node and what type of code is generated.
		 *
		 * @type {('default'|'hardware'|'alphaToCoverage')}
		 */
		this.scope = scope;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Setups the node depending on the selected scope.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node} The result node.
	 */
	setup( builder ) {

		super.setup( builder );

		const clippingContext = builder.clippingContext;
		const { intersectionPlanes, unionPlanes } = clippingContext;

		this.hardwareClipping = builder.hardwareClipping;

		// with ArrayCamera, clipping planes are in world space

		this.position = builder.camera.isArrayCamera === true ? cameraWorldMatrix.mul( vec4( positionView, 1 ) ).xyz : positionView;

		if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) {

			return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes );

		} else if ( this.scope === ClippingNode.HARDWARE ) {

			return this.setupHardwareClipping( unionPlanes, builder );

		} else {

			return this.setupDefault( intersectionPlanes, unionPlanes );

		}

	}

	/**
	 * Setups alpha to coverage.
	 *
	 * @param {Array<Vector4>} intersectionPlanes - The intersection planes.
	 * @param {Array<Vector4>} unionPlanes - The union planes.
	 * @return {Node} The result node.
	 */
	setupAlphaToCoverage( intersectionPlanes, unionPlanes ) {

		return Fn( () => {

			const distanceToPlane = float().toVar( 'distanceToPlane' );
			const distanceGradient = float().toVar( 'distanceToGradient' );

			const clipOpacity = float( 1 ).toVar( 'clipOpacity' );

			const numUnionPlanes = unionPlanes.length;

			if ( this.hardwareClipping === false && numUnionPlanes > 0 ) {

				const clippingPlanes = uniformArray( unionPlanes ).setGroup( renderGroup );

				Loop( numUnionPlanes, ( { i } ) => {

					const plane = clippingPlanes.element( i );

					distanceToPlane.assign( this.position.dot( plane.xyz ).negate().add( plane.w ) );
					distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );

					clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) );

				} );

			}

			const numIntersectionPlanes = intersectionPlanes.length;

			if ( numIntersectionPlanes > 0 ) {

				const clippingPlanes = uniformArray( intersectionPlanes ).setGroup( renderGroup );
				const intersectionClipOpacity = float( 1 ).toVar( 'intersectionClipOpacity' );

				Loop( numIntersectionPlanes, ( { i } ) => {

					const plane = clippingPlanes.element( i );

					distanceToPlane.assign( this.position.dot( plane.xyz ).negate().add( plane.w ) );
					distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );

					intersectionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() );

				} );

				clipOpacity.mulAssign( intersectionClipOpacity.oneMinus() );

			}

			diffuseColor.a.mulAssign( clipOpacity );

			diffuseColor.a.equal( 0.0 ).discard();

		} )();

	}

	/**
	 * Setups the default clipping.
	 *
	 * @param {Array<Vector4>} intersectionPlanes - The intersection planes.
	 * @param {Array<Vector4>} unionPlanes - The union planes.
	 * @return {Node} The result node.
	 */
	setupDefault( intersectionPlanes, unionPlanes ) {

		return Fn( () => {

			const numUnionPlanes = unionPlanes.length;

			if ( this.hardwareClipping === false && numUnionPlanes > 0 ) {

				const clippingPlanes = uniformArray( unionPlanes ).setGroup( renderGroup );

				Loop( numUnionPlanes, ( { i } ) => {

					const plane = clippingPlanes.element( i );
					this.position.dot( plane.xyz ).greaterThan( plane.w ).discard();

				} );

			}

			const numIntersectionPlanes = intersectionPlanes.length;

			if ( numIntersectionPlanes > 0 ) {

				const clippingPlanes = uniformArray( intersectionPlanes ).setGroup( renderGroup );
				const clipped = bool( true ).toVar( 'clipped' );

				Loop( numIntersectionPlanes, ( { i } ) => {

					const plane = clippingPlanes.element( i );
					clipped.assign( this.position.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) );

				} );

				clipped.discard();

			}

		} )();

	}

	/**
	 * Setups hardware clipping.
	 *
	 * @param {Array<Vector4>} unionPlanes - The union planes.
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node} The result node.
	 */
	setupHardwareClipping( unionPlanes, builder ) {

		const numUnionPlanes = unionPlanes.length;

		builder.enableHardwareClipping( numUnionPlanes );

		return Fn( () => {

			const clippingPlanes = uniformArray( unionPlanes ).setGroup( renderGroup );
			const hw_clip_distances = builtin( builder.getClipDistance() );

			Loop( numUnionPlanes, ( { i } ) => {

				const plane = clippingPlanes.element( i );

				const distance = this.position.dot( plane.xyz ).sub( plane.w ).negate();
				hw_clip_distances.element( i ).assign( distance );

			} );

		} )();

	}

}

ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage';
ClippingNode.DEFAULT = 'default';
ClippingNode.HARDWARE = 'hardware';

/**
 * TSL function for setting up the default clipping logic.
 *
 * @tsl
 * @function
 * @returns {ClippingNode}
 */
const clipping = () => new ClippingNode();

/**
 * TSL function for setting up alpha to coverage.
 *
 * @tsl
 * @function
 * @returns {ClippingNode}
 */
const clippingAlpha = () => new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE );

/**
 * TSL function for setting up hardware-based clipping.
 *
 * @tsl
 * @function
 * @returns {ClippingNode}
 */
const hardwareClipping = () => new ClippingNode( ClippingNode.HARDWARE );

const refreshUniforms = [
	'alphaMap',
	'alphaTest',
	'anisotropy',
	'anisotropyMap',
	'anisotropyRotation',
	'aoMap',
	'aoMapIntensity',
	'attenuationColor',
	'attenuationDistance',
	'bumpMap',
	'bumpScale',
	'clearcoat',
	'clearcoatMap',
	'clearcoatNormalMap',
	'clearcoatNormalScale',
	'clearcoatRoughness',
	'clearcoatRoughnessMap',
	'color',
	'dashOffset',
	'dashSize',
	'diffuseRoughness',
	'diffuseRoughnessMap',
	'dispersion',
	'displacementBias',
	'displacementMap',
	'displacementScale',
	'emissive',
	'emissiveIntensity',
	'emissiveMap',
	'envMap',
	'envMapIntensity',
	'envMapRotation',
	'gapSize',
	'gradientMap',
	'ior',
	'iridescence',
	'iridescenceIOR',
	'iridescenceMap',
	'iridescenceThicknessMap',
	'iridescenceThicknessMaximum',
	'iridescenceThicknessMinimum',
	'lightMap',
	'lightMapIntensity',
	'linewidth',
	'map',
	'matcap',
	'metalness',
	'metalnessMap',
	'normalMap',
	'normalScale',
	'opacity',
	'reflectivity',
	'refractionRatio',
	'retroreflectivity',
	'rotation',
	'roughness',
	'roughnessMap',
	'scale',
	'sheen',
	'sheenColor',
	'sheenColorMap',
	'sheenRoughness',
	'sheenRoughnessMap',
	'shininess',
	'size',
	'specular',
	'specularColor',
	'specularColorMap',
	'specularIntensity',
	'specularIntensityMap',
	'specularMap',
	'steps',
	'thickness',
	'thicknessMap',
	'transmission',
	'transmissionMap',
	'wireframe'
];


/**
 * A WeakMap to cache lights data for node materials.
 * Cache lights data by render ID to avoid unnecessary recalculations.
 *
 * @private
 * @type {WeakMap<LightsNode,Object>}
 */
const _lightsCache = new WeakMap();

/**
 * Holds the material data for comparison.
 *
 * @private
 * @type {WeakMap<Material,Object>}
 */
const _materialCache = new WeakMap();

/**
 * Holds the geometry data for comparison.
 *
 * @private
 * @type {WeakMap<BufferGeometry,Object>}
 */
const _geometryCache = new WeakMap();

/**
 * Holds the texture data for comparison.
 *
 * @private
 * @type {WeakMap<Texture,Object>}
 */
const _textureCache = new WeakMap();

/**
 * Holds the output target data for comparison.
 *
 * @private
 * @type {WeakMap<RenderTarget|CanvasTarget,Object>}
 */
const _outputTargetCache = new WeakMap();

/**
 * This class is used by {@link WebGPURenderer} as management component.
 * It's primary purpose is to determine whether render objects require a
 * refresh right before they are going to be rendered or not.
 */
class NodeMaterialObserver {

	/**
	 * Constructs a new node material observer.
	 *
	 * @param {NodeBuilder} builder - The node builder.
	 */
	constructor( builder ) {

		/**
		 * A node material can be used by more than one render object so the
		 * monitor must maintain a list of render objects.
		 *
		 * @type {WeakMap<RenderObject,Object>}
		 */
		this.renderObjects = new WeakMap();

		/**
		 * Whether the builder's context uses node objects or not.
		 *
		 * @type {boolean}
		 */
		this.hasNode = this.containsNode( builder );

		/**
		 * Whether the node builder's 3D object is animated or not.
		 *
		 * @type {boolean}
		 */
		this.hasAnimation = builder.object.isSkinnedMesh === true;

		/**
		 * A list of all possible material uniforms
		 *
		 * @type {Array<string>}
		 */
		this.refreshUniforms = refreshUniforms;

		/**
		 * Holds the current render ID from the node frame.
		 *
		 * @type {number}
		 * @default 0
		 */
		this.renderId = 0;

	}

	/**
	 * Returns `true` if the given render object is verified for the first time of this observer.
	 *
	 * @param {RenderObject} renderObject - The render object.
	 * @return {boolean} Whether the given render object is verified for the first time of this observer.
	 */
	firstInitialization( renderObject ) {

		const hasInitialized = this.renderObjects.has( renderObject );

		if ( hasInitialized === false ) {

			this.getRenderObjectData( renderObject );

			return true;

		}

		return false;

	}

	/**
	 * Returns `true` if the given 3D object uses instance buffers with dynamic draw usage.
	 * Such buffers must be uploaded once per render so the render object requires a full refresh.
	 *
	 * @param {Object3D} object - The 3D object.
	 * @return {boolean} Whether the given 3D object uses instance buffers with dynamic draw usage or not.
	 */
	hasDynamicInstancing( object ) {

		return object.isInstancedMesh === true && ( object.instanceMatrix.usage === DynamicDrawUsage ||
			( object.instanceColor !== null && object.instanceColor.usage === DynamicDrawUsage ) );

	}

	/**
	 * Returns `true` if the current rendering produces motion vectors.
	 *
	 * @param {Renderer} renderer - The renderer.
	 * @return {boolean} Whether the current rendering produces motion vectors or not.
	 */
	needsVelocity( renderer ) {

		const mrt = renderer.getMRT();

		return ( mrt !== null && mrt.has( 'velocity' ) );

	}

	/**
	 * Returns monitoring data for the given render object.
	 *
	 * @param {RenderObject} renderObject - The render object.
	 * @return {Object} The monitoring data.
	 */
	getRenderObjectData( renderObject ) {

		let data = this.renderObjects.get( renderObject );

		if ( data === undefined ) {

			const { geometry, object } = renderObject;

			data = {
				geometryId: geometry.id,
				geometryVersion: this.getGeometryData( geometry )._version,
				materialVersion: this.getMaterialData( renderObject.material )._version,
				attributesDisposeVersion: renderObject._geometries.attributes.disposeVersion,
				worldMatrix: object.matrixWorld.clone()
			};

			if ( object.center ) {

				data.center = object.center.clone();

			}

			if ( object.morphTargetInfluences ) {

				data.morphTargetInfluences = object.morphTargetInfluences.slice();

			}

			if ( object.isInstancedMesh === true ) {

				data.instanceMatrix = object.instanceMatrix;
				data.instanceMatrixVersion = object.instanceMatrix.version;
				data.instanceColor = object.instanceColor;
				data.instanceColorVersion = object.instanceColor !== null ? object.instanceColor.version : null;
				data.morphTextureVersion = object.morphTexture !== null ? object.morphTexture.version : null;

			}

			if ( object.isBatchedMesh === true ) {

				data.matricesTextureVersion = object._matricesTexture.version;
				data.colorsTextureVersion = object._colorsTexture !== null ? object._colorsTexture.version : null;
				data.indirectTextureVersion = object._indirectTexture.version;

			}

			if ( renderObject.bundle !== null ) {

				data.version = renderObject.bundle.version;

			}

			// global data

			data.outputTarget = this.getOutputTarget( renderObject.renderer );
			data.outputTargetVersion = this.getOutputTargetData( data.outputTarget, renderObject.context )._version;

			const { environment, environmentIntensity, environmentRotation } = renderObject.scene;

			data.environmentIntensity = environmentIntensity;
			data.environmentRotation = environmentRotation.clone();
			data.environmentVersion = environment !== null ? this.getTextureData( environment )._version : null;

			data.lights = this.getLightsData( renderObject.lightsNode.getBuiltinLights(), [] );

			this.renderObjects.set( renderObject, data );

		}

		return data;

	}

	/**
	 * Returns the current output target of the given renderer. That is the active
	 * render target or, if none is set, the output render target or the canvas target.
	 *
	 * @param {Renderer} renderer - The renderer.
	 * @return {RenderTarget|CanvasTarget} The output target.
	 */
	getOutputTarget( renderer ) {

		const renderTarget = renderer.getRenderTarget();

		return renderTarget !== null ? renderTarget : ( renderer.getOutputRenderTarget() || renderer.getCanvasTarget() );

	}

	/**
	 * Returns an output target data structure holding the size of the given output
	 * target for monitoring. Its version is incremented whenever a size change is observed.
	 *
	 * @param {RenderTarget|CanvasTarget} outputTarget - The output target.
	 * @param {RenderContext} renderContext - The render context.
	 * @return {Object} An object for monitoring the output target size.
	 */
	getOutputTargetData( outputTarget, renderContext ) {

		const { width, height } = renderContext;

		let data = _outputTargetCache.get( outputTarget );

		if ( data === undefined ) {

			data = { _version: 0, width, height };

			_outputTargetCache.set( outputTarget, data );

		} else if ( data.width !== width || data.height !== height ) {

			data.width = width;
			data.height = height;
			data._version ++;

		}

		return data;

	}

	/**
	 * Returns an attribute data structure holding the attributes versions for
	 * monitoring.
	 *
	 * @param {Object} attributes - The geometry attributes.
	 * @return {Object} An object for monitoring the versions of attributes.
	 */
	getAttributesData( attributes ) {

		const attributesData = {};

		for ( const name in attributes ) {

			const attribute = attributes[ name ];

			attributesData[ name ] = {
				id: attribute.isInterleavedBufferAttribute ? attribute.data.uuid : attribute.id,
				version: attribute.isInterleavedBufferAttribute ? attribute.data.version : attribute.version,
			};

		}

		return attributesData;

	}

	/**
	 * Returns `true` if the node builder's context (e.g. material) uses node properties.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {boolean} Whether the node builder's context uses node properties or not.
	 */
	containsNode( builder ) {

		// material

		const material = builder.material;

		for ( const property in material ) {

			if ( material[ property ] && material[ property ].isNode )
				return true;

		}

		// context

		if ( builder.context.modelViewMatrix || builder.context.modelNormalViewMatrix || builder.context.getAO || builder.context.getShadow || builder.context.getGI )
			return true;

		return false;

	}

	/**
	 * Returns a geometry data structure holding the geometry property values for
	 * monitoring.
	 *
	 * @param {BufferGeometry} geometry - The geometry.
	 * @return {Object} An object for monitoring geometry properties.
	 */
	getGeometryData( geometry ) {

		let data = _geometryCache.get( geometry );

		if ( data === undefined ) {

			data = {
				_renderId: -1,
				_version: 0,

				attributes: this.getAttributesData( geometry.attributes ),
				indexId: geometry.index ? geometry.index.id : null,
				indexVersion: geometry.index ? geometry.index.version : null,
				drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count }
			};

			// force refresh on dispose

			geometry.addEventListener( 'dispose', () => {

				data._version ++;

			} );

			_geometryCache.set( geometry, data );

		}

		return data;

	}

	/**
	 * Returns a texture data structure holding the texture state for
	 * monitoring.
	 *
	 * @param {Texture} texture - The texture.
	 * @return {Object} An object for monitoring the texture.
	 */
	getTextureData( texture ) {

		let data = _textureCache.get( texture );

		if ( data === undefined ) {

			data = { _version: 0 };

			// force refresh on dispose

			const onDispose = () => {

				data._version ++;

			};

			if ( texture.renderTarget !== null ) {

				texture.renderTarget.addEventListener( 'dispose', onDispose );

			} else {

				texture.addEventListener( 'dispose', onDispose );

			}

			_textureCache.set( texture, data );

		}

		return data;

	}

	/**
	 * Returns a data structure holding the uv transformation of the given
	 * texture for monitoring.
	 *
	 * @param {Texture} texture - The texture.
	 * @return {Object} An object for monitoring the uv transformation.
	 */
	getTextureTransformData( texture ) {

		return {
			offset: texture.offset.clone(),
			repeat: texture.repeat.clone(),
			center: texture.center.clone(),
			rotation: texture.rotation,
			matrix: texture.matrix.clone()
		};

	}

	/**
	 * Returns `true` if the uv transformation of the given texture has not changed.
	 *
	 * @param {Object} transformData - The monitored uv transformation.
	 * @param {Texture} texture - The texture.
	 * @return {boolean} Whether the uv transformation has not changed or not.
	 */
	equalsTextureTransform( transformData, texture ) {

		if ( texture.matrixAutoUpdate === true ) {

			return transformData.rotation === texture.rotation && transformData.offset.equals( texture.offset ) && transformData.repeat.equals( texture.repeat ) && transformData.center.equals( texture.center );

		}

		return transformData.matrix.equals( texture.matrix );

	}

	/**
	 * Returns a material data structure holding the material property values for
	 * monitoring.
	 *
	 * @param {Material} material - The material.
	 * @return {Object} An object for monitoring material properties.
	 */
	getMaterialData( material ) {

		let data = _materialCache.get( material );

		if ( data === undefined ) {

			data = { _renderId: -1, _version: 0 };

			for ( const property of this.refreshUniforms ) {

				const value = material[ property ];

				if ( value === undefined ) continue;

				if ( value === null ) {

					data[ property ] = null; // track unset properties

				} else if ( typeof value === 'object' && value.clone !== undefined ) {

					if ( value.isTexture === true ) {

						data[ property ] = { id: value.id, version: 0, cacheVersion: this.getTextureData( value )._version, transform: this.getTextureTransformData( value ) };

					} else {

						data[ property ] = value.clone();

					}

				} else {

					data[ property ] = value;

				}

			}

			_materialCache.set( material, data );

		}

		return data;

	}

	/**
	 * Returns `true` if the given render object has not changed its state.
	 *
	 * @param {RenderObject} renderObject - The render object.
	 * @param {Array<Light>} lightsData - The current material lights.
	 * @param {number} renderId - The current render ID.
	 * @return {boolean} Whether the given render object is equal to its cached state or not.
	 */
	equals( renderObject, lightsData, renderId ) {

		const { object, material, geometry } = renderObject;

		const renderObjectData = this.getRenderObjectData( renderObject );

		// world matrix

		if ( renderObjectData.worldMatrix.equals( object.matrixWorld ) !== true ) {

			renderObjectData.worldMatrix.copy( object.matrixWorld );

			return false;

		}

		// material

		const materialData = this.getMaterialData( renderObject.material );

		// check the material properties just once per render for all render objects

		if ( materialData._renderId !== renderId ) {

			materialData._renderId = renderId;

			let changed = false;

			for ( const property in materialData ) {

				const value = materialData[ property ];
				const mtlValue = material[ property ];

				if ( property === '_renderId' ) continue;
				if ( property === '_version' ) continue;

				if ( value === null || mtlValue === null || mtlValue === undefined ) {

					// a property was assigned or removed since the last observation so a new snapshot is required

					if ( value !== ( mtlValue === undefined ? null : mtlValue ) ) {

						if ( mtlValue === null || mtlValue === undefined ) {

							materialData[ property ] = null;

						} else if ( mtlValue.isTexture === true ) {

							materialData[ property ] = { id: mtlValue.id, version: mtlValue.version, cacheVersion: this.getTextureData( mtlValue )._version, transform: this.getTextureTransformData( mtlValue ) };

						} else if ( typeof mtlValue === 'object' && mtlValue.clone !== undefined ) {

							materialData[ property ] = mtlValue.clone();

						} else {

							materialData[ property ] = mtlValue;

						}

						changed = true;

					}

				} else if ( value.equals !== undefined ) {

					if ( value.equals( mtlValue ) === false ) {

						value.copy( mtlValue );

						changed = true;

					}

				} else if ( mtlValue.isTexture === true ) {

					const textureData = this.getTextureData( mtlValue );

					if ( value.id !== mtlValue.id || value.version !== mtlValue.version || value.cacheVersion !== textureData._version || this.equalsTextureTransform( value.transform, mtlValue ) === false ) {

						value.id = mtlValue.id;
						value.version = mtlValue.version;
						value.cacheVersion = textureData._version;
						value.transform.offset.copy( mtlValue.offset );
						value.transform.repeat.copy( mtlValue.repeat );
						value.transform.center.copy( mtlValue.center );
						value.transform.rotation = mtlValue.rotation;
						value.transform.matrix.copy( mtlValue.matrix );

						changed = true;

					}

				} else if ( value !== mtlValue ) {

					materialData[ property ] = mtlValue;

					changed = true;

				}

			}

			if ( changed === true ) materialData._version ++;

		}

		// a version mismatch means the material has changed since this render object was last refreshed

		if ( renderObjectData.materialVersion !== materialData._version ) {

			renderObjectData.materialVersion = materialData._version;

			return false;

		}

		const outputTargetData = this.getOutputTargetData( renderObjectData.outputTarget, renderObject.context );

		// a version mismatch means the output target has been resized since this render object was last refreshed

		if ( renderObjectData.outputTargetVersion !== outputTargetData._version ) {

			renderObjectData.outputTargetVersion = outputTargetData._version;

			return false;

		}

		// geometry

		if ( renderObjectData.geometryId !== geometry.id ) {

			renderObjectData.geometryId = geometry.id;
			renderObjectData.geometryVersion = this.getGeometryData( geometry )._version;

			return false;

		}

		const geometryData = this.getGeometryData( renderObject.geometry );

		// check the geometry properties just once per render for all render objects

		if ( geometryData._renderId !== renderId ) {

			geometryData._renderId = renderId;

			let changed = false;

			// attributes

			const attributes = geometry.attributes;
			const storedAttributes = geometryData.attributes;

			let currentAttributeCount = 0;
			let storedAttributeCount = 0;

			for ( const _ in attributes ) currentAttributeCount ++; // eslint-disable-line no-unused-vars

			for ( const name in storedAttributes ) {

				storedAttributeCount ++;

				const storedAttributeData = storedAttributes[ name ];
				const attribute = attributes[ name ];

				if ( attribute === undefined ) {

					// attribute was removed
					delete storedAttributes[ name ];

					changed = true;
					continue;

				}

				const id = attribute.isInterleavedBufferAttribute ? attribute.data.uuid : attribute.id;
				const version = attribute.isInterleavedBufferAttribute ? attribute.data.version : attribute.version;

				if ( storedAttributeData.id !== id || storedAttributeData.version !== version ) {

					storedAttributeData.id = id;
					storedAttributeData.version = version;

					changed = true;

				}

			}

			if ( storedAttributeCount !== currentAttributeCount ) {

				geometryData.attributes = this.getAttributesData( attributes );

				changed = true;

			}

			// check index

			const index = geometry.index;
			const currentIndexId = index ? index.id : null;
			const currentIndexVersion = index ? index.version : null;

			if ( geometryData.indexId !== currentIndexId || geometryData.indexVersion !== currentIndexVersion ) {

				geometryData.indexId = currentIndexId;
				geometryData.indexVersion = currentIndexVersion;

				changed = true;

			}

			// check drawRange

			if ( geometryData.drawRange.start !== geometry.drawRange.start || geometryData.drawRange.count !== geometry.drawRange.count ) {

				geometryData.drawRange.start = geometry.drawRange.start;
				geometryData.drawRange.count = geometry.drawRange.count;

				changed = true;

			}

			if ( changed === true ) geometryData._version ++;

		}

		// a version mismatch means the geometry has changed since this render object was last refreshed

		if ( renderObjectData.geometryVersion !== geometryData._version ) {

			renderObjectData.geometryVersion = geometryData._version;

			return false;

		}

		// morph targets

		if ( renderObjectData.morphTargetInfluences ) {

			let morphChanged = false;

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

				if ( renderObjectData.morphTargetInfluences[ i ] !== object.morphTargetInfluences[ i ] ) {

					renderObjectData.morphTargetInfluences[ i ] = object.morphTargetInfluences[ i ];
					morphChanged = true;

				}

			}

			if ( morphChanged ) return false;

		}

		// instancing

		if ( object.isInstancedMesh === true ) {

			const instanceColorVersion = object.instanceColor !== null ? object.instanceColor.version : null;
			const morphTextureVersion = object.morphTexture !== null ? object.morphTexture.version : null;

			if ( renderObjectData.instanceMatrix !== object.instanceMatrix ||
				renderObjectData.instanceMatrixVersion !== object.instanceMatrix.version ||
				renderObjectData.instanceColor !== object.instanceColor ||
				renderObjectData.instanceColorVersion !== instanceColorVersion ||
				renderObjectData.morphTextureVersion !== morphTextureVersion ) {

				renderObjectData.instanceMatrix = object.instanceMatrix;
				renderObjectData.instanceMatrixVersion = object.instanceMatrix.version;
				renderObjectData.instanceColor = object.instanceColor;
				renderObjectData.instanceColorVersion = instanceColorVersion;
				renderObjectData.morphTextureVersion = morphTextureVersion;

				return false;

			}

		}

		// batching

		if ( object.isBatchedMesh === true ) {

			const colorsTextureVersion = object._colorsTexture !== null ? object._colorsTexture.version : null;

			if ( renderObjectData.matricesTextureVersion !== object._matricesTexture.version ||
				renderObjectData.colorsTextureVersion !== colorsTextureVersion ||
				renderObjectData.indirectTextureVersion !== object._indirectTexture.version ) {

				renderObjectData.matricesTextureVersion = object._matricesTexture.version;
				renderObjectData.colorsTextureVersion = colorsTextureVersion;
				renderObjectData.indirectTextureVersion = object._indirectTexture.version;

				return false;

			}

		}

		// lights

		if ( renderObjectData.lights ) {

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

				const lightData = renderObjectData.lights[ i ];
				const currentLightData = lightsData[ i ];

				if ( lightData.map !== currentLightData.map || lightData.cacheVersion !== currentLightData.cacheVersion ||
					lightData.shadowMapWidth !== currentLightData.shadowMapWidth || lightData.shadowMapHeight !== currentLightData.shadowMapHeight ) {

					lightData.map = currentLightData.map;
					lightData.cacheVersion = currentLightData.cacheVersion;
					lightData.shadowMapWidth = currentLightData.shadowMapWidth;
					lightData.shadowMapHeight = currentLightData.shadowMapHeight;

					return false;

				}

			}

		}

		// scene

		const scene = renderObject.scene;

		if ( scene.environment !== null && material.envMap === null ) {

			const environmentVersion = this.getTextureData( scene.environment )._version;

			if ( renderObjectData.environmentIntensity !== scene.environmentIntensity ||
					renderObjectData.environmentRotation.equals( scene.environmentRotation ) === false ||
					renderObjectData.environmentVersion !== environmentVersion ) {

				renderObjectData.environmentIntensity = scene.environmentIntensity;
				renderObjectData.environmentRotation.copy( scene.environmentRotation );
				renderObjectData.environmentVersion = environmentVersion;

				return false;

			}

		}

		// center

		if ( renderObjectData.center ) {

			if ( renderObjectData.center.equals( object.center ) === false ) {

				renderObjectData.center.copy( object.center );

				return false;

			}

		}

		// bundle

		if ( renderObject.bundle !== null ) {

			renderObjectData.version = renderObject.bundle.version;

		}

		return true;

	}

	/**
	 * Returns the lights data for the given material lights.
	 *
	 * @param {Array<Light>} materialLights - The material lights.
	 * @return {Array<Object>} The lights data for the given material lights.
	 */
	getLightsData( materialLights, lights ) {

		lights.length = 0;

		for ( const light of materialLights ) {

			let data = null;

			if ( light.isSpotLight === true && light.map !== null ) {

				// only add lights that have a map

				data = { map: light.map.version, cacheVersion: this.getTextureData( light.map )._version };

			}

			if ( light.castShadow === true && light.shadow !== undefined ) {

				// resizing a shadow map recreates its textures so the bindings
				// of all related render objects must be updated

				if ( data === null ) data = {};

				data.shadowMapWidth = light.shadow.mapSize.width;
				data.shadowMapHeight = light.shadow.mapSize.height;

			}

			if ( data !== null ) lights.push( data );

		}

		return lights;

	}

	/**
	 * Returns the lights for the given lights node and render ID.
	 *
	 * @param {LightsNode} lightsNode - The lights node.
	 * @param {number} renderId - The render ID.
	 * @return {Array<Object>} The lights for the given lights node and render ID.
	 */
	getLights( lightsNode, renderId ) {

		let cached = _lightsCache.get( lightsNode );

		if ( cached === undefined ) {

			cached = { renderId: -1, lightsData: [] };
			_lightsCache.set( lightsNode, cached );

		}

		if ( cached.renderId === renderId ) {

			return cached.lightsData;

		}

		cached.renderId = renderId;
		this.getLightsData( lightsNode.getBuiltinLights(), cached.lightsData );

		return cached.lightsData;

	}

	/**
	 * Returns `true` if a vertex buffer or the index of the given render object
	 * has been disposed since the last check.
	 *
	 * @param {RenderObject} renderObject - The render object.
	 * @param {Object} renderObjectData - The cached render object data.
	 * @return {boolean} Whether attributes of the render object have been disposed or not.
	 */
	hasDisposedAttributes( renderObject, renderObjectData ) {

		const attributes = renderObject._geometries.attributes;

		for ( const vertexBuffer of renderObject.getVertexBuffers() ) {

			if ( attributes.has( vertexBuffer ) === false ) return true;

		}

		const index = renderObject.getIndex();

		if ( index !== null && attributes.has( index ) === false ) return true;

		renderObjectData.attributesDisposeVersion = attributes.disposeVersion;

		return false;

	}

	/**
	 * Checks if the given render object requires a refresh.
	 *
	 * @param {RenderObject} renderObject - The render object.
	 * @param {NodeFrame} nodeFrame - The current node frame.
	 * @return {number} The refresh type, see {@link RenderObjectRefreshType}.
	 */
	needsRefresh( renderObject, nodeFrame ) {

		if ( this.hasNode || this.hasAnimation || this.hasDynamicInstancing( renderObject.object ) || this.firstInitialization( renderObject ) || this.needsVelocity( nodeFrame.renderer ) )
			return RenderObjectRefreshType.FULL;

		const { renderId } = nodeFrame;

		let refreshType = RenderObjectRefreshType.NONE;

		// shared UBOs are potentially never updated when objects don't change. Below block
		// make sure these UBOs are updated at least once.

		if ( this.renderId !== renderId ) {

			this.renderId = renderId;

			// no early out here. instead, use the equals() code path below so the internal cache state gets synched

			refreshType = RenderObjectRefreshType.SHARED;

		}

		// bindings might refer to per-target resources so a target switch needs a full refresh

		const renderObjectData = this.getRenderObjectData( renderObject );
		const outputTarget = this.getOutputTarget( nodeFrame.renderer );

		if ( renderObjectData.outputTarget !== outputTarget ) {

			renderObjectData.outputTarget = outputTarget;

			refreshType = RenderObjectRefreshType.FULL;

		}

		const attributes = renderObject._geometries.attributes;

		// re-upload disposed attributes. checked before the static early out since other geometries can destroy shared buffers

		if ( renderObjectData.attributesDisposeVersion !== attributes.disposeVersion ) {

			if ( this.hasDisposedAttributes( renderObject, renderObjectData ) ) refreshType = RenderObjectRefreshType.FULL;

		}

		const isStatic = renderObject.object.static === true;
		const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && renderObjectData.version === renderObject.bundle.version;

		if ( isStatic || isBundle )
			return refreshType;

		const lightsData = this.getLights( renderObject.lightsNode, renderId );

		if ( this.equals( renderObject, lightsData, renderId ) === false ) {

			refreshType = RenderObjectRefreshType.FULL;

		}

		return refreshType;

	}

}

// See: https://casual-effects.com/research/Wyman2017Hashed/index.html

const ALPHA_HASH_SCALE = 0.05; // Derived from trials only, and may be changed.

const hash2D = /*@__PURE__*/ Fn( ( [ value ] ) => {

	return fract( mul( 1.0e4, sin( mul( 17.0, value.x ).add( mul( 0.1, value.y ) ) ) ).mul( add( 0.1, abs( sin( mul( 13.0, value.y ).add( value.x ) ) ) ) ) );

}, { value: 'vec2', return: 'float' } );

const hash3D = /*@__PURE__*/ Fn( ( [ value ] ) => {

	return hash2D( vec2( hash2D( value.xy ), value.z ) );

}, { value: 'vec3', return: 'float' } );

const getAlphaHashThreshold = /*@__PURE__*/ Fn( ( [ position ] ) => {

	// Find the discretized derivatives of our coordinates
	const maxDeriv = max(
		length( dFdx( position.xyz ) ),
		length( dFdy( position.xyz ) )
	);

	const pixScale = float( 1 ).div( float( ALPHA_HASH_SCALE ).mul( maxDeriv ) ).toVar( 'pixScale' );

	// Find two nearest log-discretized noise scales
	const pixScales = vec2(
		exp2( floor( log2( pixScale ) ) ),
		exp2( ceil( log2( pixScale ) ) )
	);

	// Compute alpha thresholds at our two noise scales
	const alpha = vec2(
		hash3D( floor( pixScales.x.mul( position.xyz ) ) ),
		hash3D( floor( pixScales.y.mul( position.xyz ) ) ),
	);

	// Factor to interpolate lerp with
	const lerpFactor = fract( log2( pixScale ) );

	// Interpolate alpha threshold from noise at two scales
	const x = add( mul( lerpFactor.oneMinus(), alpha.x ), mul( lerpFactor, alpha.y ) );

	// Pass into CDF to compute uniformly distrib threshold
	const a = min( lerpFactor, lerpFactor.oneMinus() );
	const cases = vec3(
		x.mul( x ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ),
		x.sub( mul( 0.5, a ) ).div( sub( 1.0, a ) ),
		sub( 1.0, sub( 1.0, x ).mul( sub( 1.0, x ) ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ) ) );

	// Find our final, uniformly distributed alpha threshold (ατ)
	const threshold = x.lessThan( a.oneMinus() ).select( x.lessThan( a ).select( cases.x, cases.y ), cases.z );

	// Avoids ατ == 0. Could also do ατ =1-ατ
	return clamp( threshold, 1.0e-6, 1.0 );

}, { position: 'vec3', return: 'float' } );

/**
 * An attribute node for representing vertex colors.
 *
 * @augments AttributeNode
 */
class VertexColorNode extends AttributeNode {

	static get type() {

		return 'VertexColorNode';

	}

	/**
	 * Constructs a new vertex color node.
	 *
	 * @param {number} index - The attribute index.
	 */
	constructor( index ) {

		super( null, 'vec4' );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isVertexColorNode = true;

		/**
		 * The attribute index to enable more than one sets of vertex colors.
		 *
		 * @type {number}
		 * @default 0
		 */
		this.index = index;

	}

	/**
	 * Overwrites the default implementation by honoring the attribute index.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The attribute name.
	 */
	getAttributeName( /*builder*/ ) {

		const index = this.index;

		return 'color' + ( index > 0 ? index : '' );

	}

	generate( builder ) {

		const attributeName = this.getAttributeName( builder );
		const geometryAttribute = builder.hasGeometryAttribute( attributeName );

		let result;

		if ( geometryAttribute === true ) {

			result = super.generate( builder );

		} else {

			// Vertex color fallback should be white
			result = builder.generateConst( this.nodeType, new Vector4( 1, 1, 1, 1 ) );

		}

		return result;

	}

	serialize( data ) {

		super.serialize( data );

		data.index = this.index;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.index = data.index;

	}

}

/**
 * TSL function for creating a reference node.
 *
 * @tsl
 * @function
 * @param {number} [index=0] - The attribute index.
 * @returns {VertexColorNode}
 */
const vertexColor = ( index = 0 ) => new VertexColorNode( index );

/**
 * Base class for all node materials.
 *
 * @augments Material
 */
class NodeMaterial extends Material {

	static get type() {

		return 'NodeMaterial';

	}

	/**
	 * Represents the type of the node material.
	 *
	 * @type {string}
	 */
	get type() {

		return this.constructor.type;

	}

	set type( _value ) { /* */ }

	/**
	 * Constructs a new node material.
	 */
	constructor() {

		super();

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isNodeMaterial = true;

		/**
		 * Whether this material is affected by fog or not.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.fog = true;

		/**
		 * Whether this material is affected by lights or not.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.lights = false;

		/**
		 * Node materials which set their `lights` property to `true`
		 * are affected by all lights of the scene. Sometimes selective
		 * lighting is wanted which means only _some_ lights in the scene
		 * affect a material. This can be achieved by creating an instance
		 * of {@link LightsNode} with a list of selective
		 * lights and assign the node to this property.
		 *
		 * ```js
		 * const customLightsNode = lights( [ light1, light2 ] );
		 * material.lightsNode = customLightsNode;
		 * ```
		 *
		 * @type {?LightsNode}
		 * @default null
		 */
		this.lightsNode = null;

		/**
		 * The environment of node materials can be defined by an environment
		 * map assigned to the `envMap` property or by `Scene.environment`
		 * if the node material is a PBR material. This node property allows to overwrite
		 * the default behavior and define the environment with a custom node.
		 *
		 * ```js
		 * material.envNode = pmremTexture( renderTarget.texture );
		 * ```
		 *
		 * @type {?Node<vec3>}
		 * @default null
		 */
		this.envNode = null;

		/**
		 * The lighting of node materials might be influenced by ambient occlusion.
		 * The default AO is inferred from an ambient occlusion map assigned to `aoMap`
		 * and the respective `aoMapIntensity`. This node property allows to overwrite
		 * the default and define the ambient occlusion with a custom node instead.
		 *
		 * If you don't want to overwrite the diffuse color but modify the existing
		 * values instead, use {@link materialAO}.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.aoNode = null;

		/**
		 * The diffuse color of node materials is by default inferred from the
		 * `color` and `map` properties. This node property allows to overwrite the default
		 * and define the diffuse color with a node instead.
		 *
		 * ```js
		 * material.colorNode = color( 0xff0000 ); // define red color
		 * ```
		 *
		 * If you don't want to overwrite the diffuse color but modify the existing
		 * values instead, use {@link materialColor}.
		 *
		 * ```js
		 * material.colorNode = materialColor.mul( color( 0xff0000 ) ); // give diffuse colors a red tint
		 * ```
		 *
		 * @type {?Node<vec3>}
		 * @default null
		 */
		this.colorNode = null;

		/**
		 * The normals of node materials are by default inferred from the `normalMap`/`normalScale`
		 * or `bumpMap`/`bumpScale` properties. This node property allows to overwrite the default
		 * and define the normals with a node instead.
		 *
		 * If you don't want to overwrite the normals but modify the existing values instead,
		 * use {@link materialNormal}.
		 *
		 * @type {?Node<vec3>}
		 * @default null
		 */
		this.normalNode = null;

		/**
		 * The opacity of node materials is by default inferred from the `opacity`
		 * and `alphaMap` properties. This node property allows to overwrite the default
		 * and define the opacity with a node instead.
		 *
		 * If you don't want to overwrite the opacity but modify the existing
		 * value instead, use {@link materialOpacity}.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.opacityNode = null;

		/**
		 * This node can be used to implement a variety of filter-like effects. The idea is
		 * to store the current rendering into a texture e.g. via `viewportSharedTexture()`, use it
		 * to create an arbitrary effect and then assign the node composition to this property.
		 * Everything behind the object using this material will now be affected by a filter.
		 *
		 * ```js
		 * const material = new NodeMaterial()
		 * material.transparent = true;
		 *
		 * // everything behind the object will be monochromatic
		 * material.backdropNode = saturation( viewportSharedTexture().rgb, 0 );
		 * ```
		 *
		 * Backdrop computations are part of the lighting so only lit materials can use this property.
		 *
		 * @type {?Node<vec3>}
		 * @default null
		 */
		this.backdropNode = null;

		/**
		 * This node allows to modulate the influence of `backdropNode` to the outgoing light.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.backdropAlphaNode = null;

		/**
		 * The alpha test of node materials is by default inferred from the `alphaTest`
		 * property. This node property allows to overwrite the default and define the
		 * alpha test with a node instead.
		 *
		 * If you don't want to overwrite the alpha test but modify the existing
		 * value instead, use {@link materialAlphaTest}.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.alphaTestNode = null;


		/**
		 * Discards the fragment if the mask value is `false`.
		 *
		 * @type {?Node<bool>}
		 * @default null
		 */
		this.maskNode = null;

		/**
		 * This node can be used to implement a shadow mask for the material.
		 *
		 * @type {?Node<bool>}
		 * @default null
		 */
		this.maskShadowNode = null;

		/**
		 * The local vertex positions are computed based on multiple factors like the
		 * attribute data, morphing, skinning, instancing or batching. This node property
		 * allows to overwrite the default and define local vertex positions with nodes instead.
		 *
		 * If you don't want to overwrite the vertex positions but modify the existing
		 * values instead, use {@link positionLocal}.
		 *
		 *```js
		 * material.positionNode = positionLocal.add( displace );
		 * ```
		 *
		 * @type {?Node<vec3>}
		 * @default null
		 */
		this.positionNode = null;

		/**
		 * This node property is intended for logic which modifies geometry data once or per animation step.
		 * Apps usually place such logic randomly in initialization routines or in the animation loop.
		 * `geometryNode` is intended as a dedicated API so there is an intended spot where geometry modifications
		 * can be implemented.
		 *
		 * The idea is to assign a `Fn` definition that holds the geometry modification logic. A typical example
		 * would be a GPU based particle system that provides a node material for usage on app level. The particle
		 * simulation would be implemented as compute shaders and managed inside a `Fn` function. This function is
		 * eventually assigned to `geometryNode`.
		 *
		 * @type {?Function}
		 * @default null
		 */
		this.geometryNode = null;

		/**
		 * Allows to overwrite depth values in the fragment shader.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.depthNode = null;

		/**
		 * Allows to overwrite the position used for shadow map rendering which
		 * is by default {@link positionWorld}, the vertex position
		 * in world space.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.receivedShadowPositionNode = null;

		/**
		 * Allows to overwrite the geometry position used for shadow map projection which
		 * is by default {@link positionLocal}, the vertex position in local space.
		 *
		 * @type {?Node<float>}
		 * @default null
		 */
		this.castShadowPositionNode = null;

		/**
		 * This node can be used to influence how an object using this node material
		 * receive shadows.
		 *
		 * ```js
		 * const totalShadows = float( 1 ).toVar();
		 * material.receivedShadowNode = Fn( ( [ shadow ] ) => {
		 * 	totalShadows.mulAssign( shadow );
		 * 	//return float( 1 ); // bypass received shadows
		 * 	return shadow.mix( color( 0xff0000 ), 1 ); // modify shadow color
		 * } );
		 *
		 * @type {?(Function|FunctionNode<vec4>)}
		 * @default null
		 */
		this.receivedShadowNode = null;

		/**
		 * This node can be used to influence how an object using this node material
		 * casts shadows. To apply a color to shadows, you can simply do:
		 *
		 * ```js
		 * material.castShadowNode = vec4( 1, 0, 0, 1 );
		 * ```
		 *
		 * Which can be nice to fake colored shadows of semi-transparent objects. It
		 * is also common to use the property with `Fn` function so checks are performed
		 * per fragment.
		 *
		 * ```js
		 * materialCustomShadow.castShadowNode = Fn( () => {
		 * 	hash( vertexIndex ).greaterThan( 0.5 ).discard();
		 * 	return materialColor;
		 * } )();
		 *  ```
		 *
		 * @type {?Node<vec4>}
		 * @default null
		 */
		this.castShadowNode = null;

		/**
		 * This node can be used to overwrite the final output of the material.
		 *
		 * Unlike {@link NodeMaterial#fragmentNode}, the built-in material logic
		 * (diffuse color, lighting, etc.) is still evaluated; assigning a node
		 * only replaces the resulting output color.
		 *
		 * @type {?Node<vec4>}
		 * @default null
		 */
		this.outputNode = null;

		/**
		 * MRT configuration is done on renderer or pass level. This node allows to
		 * overwrite what values are written into MRT targets on material level. This
		 * can be useful for implementing selective FX features that should only affect
		 * specific objects.
		 *
		 * @type {?MRTNode}
		 * @default null
		 */
		this.mrtNode = null;

		/**
		 * This node property can be used if you need complete freedom in implementing
		 * the fragment shader. Assigning a node will replace the built-in material
		 * logic used in the fragment stage.
		 *
		 * @type {?Node<vec4>}
		 * @default null
		 */
		this.fragmentNode = null;

		/**
		 * This node property can be used if you need complete freedom in implementing
		 * the vertex shader. Assigning a node will replace the built-in material logic
		 * used in the vertex stage.
		 *
		 * @type {?Node<vec4>}
		 * @default null
		 */
		this.vertexNode = null;

		/**
		 * This node can be used as a global context management component for this material.
		 *
		 * @type {?ContextNode}
		 * @default null
		 */
		this.contextNode = null;

	}

	/**
	 * Returns an array of child nodes for this material.
	 *
	 * @private
	 * @returns {Array<{property: string, childNode: Node}>}
	 */
	_getNodeChildren() {

		const children = [];

		for ( const property of Object.getOwnPropertyNames( this ) ) {

			if ( property.startsWith( '_' ) === true ) continue;

			const object = this[ property ];

			if ( object && object.isNode === true ) {

				children.push( { property, childNode: object } );

			}

		}

		return children;

	}

	/**
	 * Allows to define a custom cache key that influence the material key computation
	 * for render objects.
	 *
	 * @return {string} The custom cache key.
	 */
	customProgramCacheKey() {

		const values = [];

		for ( const { property, childNode } of this._getNodeChildren() ) {

			values.push( hashString( property.slice( 0, -4 ) ), childNode.getCacheKey( true ) );

		}

		return this.type + hashArray( values );

	}

	/**
	 * Builds this material with the given node builder.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	build( builder ) {

		this.setup( builder );

	}

	/**
	 * Setups a node material observer with the given builder.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {NodeMaterialObserver} The node material observer.
	 */
	setupObserver( builder ) {

		return new NodeMaterialObserver( builder );

	}

	/**
	 * Setups the vertex and fragment stage of this node material.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setup( builder ) {

		builder.context.setupNormal = () => subBuild( this.setupNormal( builder ), 'NORMAL', 'vec3' );
		builder.context.setupPositionView = () => this.setupPositionView( builder );
		builder.context.setupModelViewProjection = () => this.setupModelViewProjection( builder );

		const renderer = builder.renderer;
		const renderTarget = renderer.getRenderTarget();

		// < VERTEX STAGE >

		builder.addStack();

		const mvp = this.setupVertex( builder );

		const vertexNode = subBuild( this.vertexNode || mvp, 'VERTEX' );

		builder.context.clipSpace = vertexNode;

		builder.stack.outputNode = vertexNode;

		this.setupHardwareClipping( builder );

		if ( this.geometryNode !== null ) {

			builder.stack.outputNode = builder.stack.outputNode.bypass( this.geometryNode );

		}

		builder.addFlow( 'vertex', builder.removeStack() );

		// < FRAGMENT STAGE >

		builder.addStack();

		let resultNode;

		const clippingNode = this.setupClipping( builder );

		if ( this.depthWrite === true || this.depthTest === true ) {

			// only write depth if depth buffer is configured

			if ( renderTarget !== null ) {

				if ( renderTarget.depthBuffer === true ) this.setupDepth( builder );

			} else {

				if ( renderer.depth === true ) this.setupDepth( builder );

			}

		}

		if ( this.fragmentNode === null ) {

			this.setupDiffuseColor( builder );
			this.setupAmbientOcclusion( builder );
			this.setupVariants( builder );

			const outgoingLightNode = this.setupLighting( builder );

			if ( clippingNode !== null ) builder.stack.addToStack( clippingNode );

			// force unsigned floats - useful for RenderTargets

			const basicOutput = vec4( outgoingLightNode, diffuseColor.a ).max( 0 );

			resultNode = this.setupOutput( builder, basicOutput );

			// OUTPUT NODE

			output.assign( resultNode );

			//

			const isCustomOutput = this.outputNode !== null;

			if ( isCustomOutput ) resultNode = this.outputNode;

			//

			if ( builder.context.getOutput ) {

				resultNode = builder.context.getOutput( resultNode, builder );

			}

			// MRT

			if ( renderTarget !== null ) {

				const mrt = renderer.getMRT();
				const materialMRT = this.mrtNode;

				if ( mrt !== null ) {

					if ( isCustomOutput ) output.assign( resultNode );

					resultNode = mrt;

					if ( materialMRT !== null ) {

						resultNode = mrt.merge( materialMRT );

					}

				} else if ( materialMRT !== null ) {

					resultNode = materialMRT;

				}

			}

		} else {

			let fragmentNode = this.fragmentNode;

			if ( fragmentNode.isOutputStructNode !== true ) {

				fragmentNode = fragmentNode.convert( builder.getOutputType() );

			}

			resultNode = this.setupOutput( builder, fragmentNode );

		}

		builder.stack.outputNode = resultNode;

		builder.addFlow( 'fragment', builder.removeStack() );

		// < OBSERVER >

		builder.observer = this.setupObserver( builder );

	}

	/**
	 * Setups the clipping node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {ClippingNode} The clipping node.
	 */
	setupClipping( builder ) {

		if ( builder.clippingContext === null ) return null;

		const { unionPlanes, intersectionPlanes } = builder.clippingContext;

		let result = null;

		if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) {

			const samples = builder.renderer.currentSamples;

			if ( this.alphaToCoverage && samples > 1 ) {

				// to be added to flow when the color/alpha value has been determined
				result = clippingAlpha();

			} else {

				builder.stack.addToStack( clipping() );

			}

		}

		return result;

	}

	/**
	 * Setups the hardware clipping if available on the current device.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setupHardwareClipping( builder ) {

		builder.hardwareClipping = false;

		if ( builder.clippingContext === null ) return;

		const candidateCount = builder.clippingContext.unionPlanes.length;

		// 8 planes supported by WebGL ANGLE_clip_cull_distance and WebGPU clip-distances

		if ( candidateCount > 0 && candidateCount <= 8 && builder.isAvailable( 'clipDistance' ) ) {

			builder.stack.addToStack( hardwareClipping() );

			builder.hardwareClipping = true;

		}

		return;

	}

	/**
	 * Setups the depth of this material.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setupDepth( builder ) {

		const { renderer, camera } = builder;

		// Depth

		let depthNode = this.depthNode;

		if ( depthNode === null ) {

			const mrt = renderer.getMRT();

			if ( mrt && mrt.has( 'depth' ) ) {

				depthNode = mrt.get( 'depth' );

			} else if ( renderer.logarithmicDepthBuffer === true ) {

				if ( camera.isOrthographicCamera !== true ) {

					depthNode = viewZToLogarithmicDepth( positionView.z, cameraNear, cameraFar );

				} else {

					depthNode = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );

				}

			}

		}

		if ( depthNode !== null ) {

			depth.assign( depthNode ).toStack();

		}

	}

	/**
	 * Setups the position node in view space. This method exists
	 * so derived node materials can modify the implementation e.g. sprite materials.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node<vec3>} The position in view space.
	 */
	setupPositionView( /*builder*/ ) {

		return modelViewMatrix.mul( positionLocal ).xyz;

	}

	/**
	 * Setups the position in clip space.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node<vec4>} The position in view space.
	 */
	setupModelViewProjection( /*builder*/ ) {

		return cameraProjectionMatrix.mul( positionView );

	}

	/**
	 * Setups the logic for the vertex stage.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node<vec4>} The position in clip space.
	 */
	setupVertex( builder ) {

		builder.addStack();

		this.setupPosition( builder );

		builder.context.position = builder.removeStack();

		return modelViewProjection;

	}

	/**
	 * Setups the computation of the position in local space.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node<vec3>} The position in local space.
	 */
	setupPosition( builder ) {

		const { object, geometry } = builder;

		if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) {

			morphReference( object );

		}

		if ( object.isSkinnedMesh === true ) {

			skinning( object );

		}

		if ( this.displacementMap ) {

			const displacementMap = materialReference( 'displacementMap', 'texture' );
			const displacementScale = materialReference( 'displacementScale', 'float' );
			const displacementBias = materialReference( 'displacementBias', 'float' );

			positionLocal.addAssign( normalLocal.normalize().mul( ( displacementMap.x.mul( displacementScale ).add( displacementBias ) ) ) );

		}

		if ( object.isBatchedMesh ) {

			batch( object );

		}

		if ( ( object.isInstancedMesh && object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) ) {

			instancedMesh( object );

		}

		if ( this.positionNode !== null ) {

			positionLocal.assign( subBuild( this.positionNode, 'POSITION', 'vec3' ) );

		}

		return positionLocal;

	}

	/**
	 * Setups the computation of the material's diffuse color.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setupDiffuseColor( builder ) {

		const { object, geometry } = builder;

		// MASK

		if ( this.maskNode !== null ) {

			// Discard if the mask is `false`

			bool( this.maskNode ).not().discard();

		}

		// COLOR

		let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor;

		// VERTEX COLORS

		let vertexColorNode = null;

		if ( this.vertexColors === true && geometry.hasAttribute( 'color' ) ) {

			vertexColorNode = vertexColor();

		}

		// INSTANCED COLORS

		if ( object.instanceColor ) {

			vertexColorNode = vertexColorNode !== null ? vertexColorNode.mul( instanceColor ) : instanceColor;

		}

		if ( object.isBatchedMesh && object._colorsTexture ) {

			vertexColorNode = vertexColorNode !== null ? vertexColorNode.mul( batchColor ) : batchColor;

		}

		// MORPHED COLORS

		if ( this.vertexColors === true && geometry.morphAttributes.color !== undefined ) {

			vertexColorNode = morphColor( vec4( vertexColorNode !== null ? vertexColorNode : 1 ) );

		}

		if ( vertexColorNode !== null ) {

			colorNode = colorNode.mul( vertexColorNode );

		}

		// DIFFUSE COLOR

		diffuseColor.assign( colorNode );

		// OPACITY

		const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
		diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ) );

		// ALPHA TEST

		let alphaTestNode = null;

		if ( this.alphaTestNode !== null || this.alphaTest > 0 ) {

			alphaTestNode = this.alphaTestNode !== null ? float( this.alphaTestNode ) : materialAlphaTest;

			if ( this.alphaToCoverage === true ) {

				diffuseColor.a = smoothstep( alphaTestNode, alphaTestNode.add( fwidth( diffuseColor.a ) ), diffuseColor.a );
				diffuseColor.a.lessThanEqual( 0 ).discard();

			} else {

				diffuseColor.a.lessThanEqual( alphaTestNode ).discard();

			}

		}

		// ALPHA HASH

		if ( this.alphaHash === true ) {

			diffuseColor.a.lessThan( getAlphaHashThreshold( positionLocal ) ).discard();

		}

		// OPAQUE

		if ( builder.isOpaque() ) {

			diffuseColor.a.assign( 1.0 );

		}

	}

	/**
	 * Abstract interface method that can be implemented by derived materials
	 * to setup material-specific node variables.
	 *
	 * @abstract
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setupVariants( /*builder*/ ) {

		// Interface function.

	}

	/**
	 * Setups the outgoing light node variable
	 *
	 * @return {Node<vec3>} The outgoing light node.
	 */
	setupOutgoingLight() {

		return ( this.lights === true ) ? vec3( 0 ) : diffuseColor.rgb;

	}

	/**
	 * Setups the normal node from the material.
	 *
	 * @return {Node<vec3>} The normal node.
	 */
	setupNormal() {

		return this.normalNode ? vec3( this.normalNode ) : materialNormal;

	}

	/**
	 * Setups the environment node from the material.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node<vec4>} The environment node.
	 */
	setupEnvironment( /*builder*/ ) {

		let node = null;

		if ( this.envNode ) {

			node = this.envNode;

		} else if ( this.envMap ) {

			node = this.envMap.isCubeTexture ? materialReference( 'envMap', 'cubeTexture' ) : materialReference( 'envMap', 'texture' );

		}

		return node;

	}

	/**
	 * Setups the light map node from the material.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node<vec3>} The light map node.
	 */
	setupLightMap( builder ) {

		let node = null;

		if ( builder.material.lightMap ) {

			node = new IrradianceNode( materialLightMap );

		}

		return node;

	}

	/**
	 * Setups the lights node based on the scene, environment and material.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {LightingNode<Array>} The lights node.
	 */
	setupMaterialLightings( builder ) {

		const materialLightsNode = [];

		const envNode = this.setupEnvironment( builder );

		if ( envNode && envNode.isLightingNode ) {

			materialLightsNode.push( envNode );

		}

		const lightMapNode = this.setupLightMap( builder );

		if ( lightMapNode && lightMapNode.isLightingNode ) {

			materialLightsNode.push( lightMapNode );

		}

		if ( builder.context.ambientOcclusion ) {

			materialLightsNode.push( new AONode( builder.context.ambientOcclusion ) );

		}

		if ( builder.context.getGI ) {

			const giNode = builder.context.getGI( null, builder );

			if ( giNode !== null ) {

				materialLightsNode.push( new IrradianceNode( giNode ) );

			}

		}

		return materialLightsNode;

	}

	/**
	 * Setups the ambient occlusion node from the material.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node} The ambient occlusion node.
	 */
	setupAmbientOcclusion( builder ) {

		let aoNode = this.aoNode;

		if ( aoNode === null && builder.material.aoMap ) {

			aoNode = materialAO;

		}

		if ( builder.context.getAO ) {

			aoNode = builder.context.getAO( aoNode, builder );

		}

		if ( aoNode !== null ) {

			ambientOcclusion.assign( aoNode );

			builder.context.ambientOcclusion = ambientOcclusion;

		}

	}

	/**
	 * This method should be implemented by most derived materials
	 * since it defines the material's lighting model.
	 *
	 * @abstract
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {LightingModel} The lighting model.
	 */
	setupLightingModel( /*builder*/ ) {

		// Interface function.

	}

	/**
	 * Setups the outgoing light node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Node<vec3>} The outgoing light node.
	 */
	setupLighting( builder ) {

		const { material } = builder;
		const { backdropNode, backdropAlphaNode, emissiveNode } = this;

		// OUTGOING LIGHT

		const sceneLighting = this.lights === true && builder.renderer.lighting.enabled;
		const lights = sceneLighting || this.lightsNode !== null;

		const materialLightings = sceneLighting ? this.setupMaterialLightings( builder ) : [];
		const lightsNode = lights ? ( this.lightsNode || builder.lightsNode ) : null;

		let outgoingLightNode = this.setupOutgoingLight( builder );

		if ( lightsNode && ( materialLightings.length > 0 || lightsNode.getScope().hasLights ) ) {

			const lightingModel = this.setupLightingModel( builder ) || null;

			outgoingLightNode = lightingContext( lightsNode, lightingModel, materialLightings, backdropNode, backdropAlphaNode );

		} else if ( backdropNode !== null ) {

			outgoingLightNode = vec3( backdropAlphaNode !== null ? mix( outgoingLightNode, backdropNode, backdropAlphaNode ) : backdropNode );

		}

		// EMISSIVE

		if ( ( emissiveNode && emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) {

			emissive.assign( vec3( emissiveNode ? emissiveNode : materialEmissive ) );

			outgoingLightNode = outgoingLightNode.add( emissive );

		}

		return outgoingLightNode;

	}

	/**
	 * Setup the fog.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node<vec4>} outputNode - The existing output node.
	 * @return {Node<vec4>} The output node.
	 */
	setupFog( builder, outputNode ) {

		const fogNode = builder.fogNode;

		if ( fogNode ) {

			output.assign( outputNode );

			outputNode = vec4( fogNode.toVar() );

		}

		return outputNode;

	}

	/**
	 * Setups premultiplied alpha.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node<vec4>} outputNode - The existing output node.
	 * @return {Node<vec4>} The output node.
	 */
	setupPremultipliedAlpha( builder, outputNode ) {

		return premultiplyAlpha( outputNode );

	}

	/**
	 * Setups the output node.
	 *
	 * This method can be implemented by derived materials to extend the functionality
	 * of the material's output or replace it altogether.
	 *
	 * ```js
	 * class ColoredShadowMaterial extends MeshPhongNodeMaterial {
	 *   constructor( parameters ) {
	 *     super( parameters );
	 *     this._shadeColor = uniform( new Color( parameters.shadeColor ?? 0xff0000 ) );
	 *   }
	 *
	 *   setupOutput( builder, outputNode ) {
	 *	   // Modify the native output of the MeshPhongNodeMaterial fragment shader
	 *     const brightness = min( outputNode.r, 1.0 );
	 *     const mixedColor = mix( this._shadeColor, diffuseColor.rgb, brightness );
	 *	   // Return new output back into NodeMaterial flow
	 *     return super.setupOutput( builder, vec4( mixedColor, outputNode.a ) );
	 *   }
	 * }
	 * ```
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node<vec4>} outputNode - The existing output node.
	 * @return {Node<vec4>} The output node.
	 */
	setupOutput( builder, outputNode ) {

		// FOG

		if ( this.fog === true ) {

			outputNode = this.setupFog( builder, outputNode );

		}

		// PREMULTIPLIED ALPHA

		if ( this.premultipliedAlpha === true ) {

			outputNode = this.setupPremultipliedAlpha( builder, outputNode );

		}

		return outputNode;

	}

	/**
	 * Most classic material types have a node pendant e.g. for `MeshBasicMaterial`
	 * there is `MeshBasicNodeMaterial`. This utility method is intended for
	 * defining all material properties of the classic type in the node type.
	 *
	 * @param {Material} material - The material to copy properties with their values to this node material.
	 */
	setDefaultValues( material ) {

		// This approach is to reuse the native refreshUniforms*
		// and turn available the use of features like transmission and environment in core

		for ( const property in material ) {

			const value = material[ property ];

			if ( this[ property ] === undefined ) {

				this[ property ] = value;

				if ( value && value.clone ) this[ property ] = value.clone();

			}

		}

		const descriptors = Object.getOwnPropertyDescriptors( material.constructor.prototype );

		for ( const key in descriptors ) {

			if ( Object.getOwnPropertyDescriptor( this.constructor.prototype, key ) === undefined && descriptors[ key ].get !== undefined ) {

				Object.defineProperty( this.constructor.prototype, key, descriptors[ key ] );

			}

		}

	}

	/**
	 * Serializes this material to JSON.
	 *
	 * @param {?(Object|string)} meta - The meta information for serialization.
	 * @return {Object} The serialized node.
	 */
	toJSON( meta ) {

		const isRoot = ( meta === undefined || typeof meta === 'string' );

		if ( isRoot ) {

			meta = {
				textures: {},
				images: {},
				nodes: {}
			};

		}

		const data = Material.prototype.toJSON.call( this, meta );
		data.inputNodes = {};

		for ( const { property, childNode } of this._getNodeChildren() ) {

			data.inputNodes[ property ] = childNode.toJSON( meta ).uuid;

		}

		// TODO: Copied from Object3D.toJSON

		function extractFromCache( cache ) {

			const values = [];

			for ( const key in cache ) {

				const data = cache[ key ];
				delete data.metadata;
				values.push( data );

			}

			return values;

		}

		if ( isRoot ) {

			const textures = extractFromCache( meta.textures );
			const images = extractFromCache( meta.images );
			const nodes = extractFromCache( meta.nodes );

			if ( textures.length > 0 ) data.textures = textures;
			if ( images.length > 0 ) data.images = images;
			if ( nodes.length > 0 ) data.nodes = nodes;

		}

		return data;

	}

	/**
	 * Copies the common properties of the given material to this instance.
	 *
	 * @param {Material} source - The material to copy.
	 * @return {NodeMaterial} A reference to this node material.
	 */
	copy( source ) {

		for ( let prototype = Object.getPrototypeOf( this ); prototype !== Object.prototype; prototype = Object.getPrototypeOf( prototype ) ) {

			const descriptors = Object.getOwnPropertyDescriptors( prototype );

			for ( const property in descriptors ) {

				if ( descriptors[ property ].set !== undefined && source[ property ] !== undefined ) {

					const value = source[ property ];

					if ( this[ property ] && this[ property ].copy !== undefined ) {

						this[ property ].copy( value );

					} else {

						this[ property ] = value;

					}

				}

			}

		}

		for ( const property in this ) {

			// Skip internal/private properties (starting with '_'), flags (starting with 'is' + uppercase),
			// and properties that are handled separately or should not be copied (id, uuid, version, type, userData, clippingPlanes).

			if ( /^(?:is[A-Z]|_)|^(?:id|uuid|version|type|userData|clippingPlanes)$/.test( property ) ) continue;

			if ( this[ property ] !== undefined && source[ property ] !== undefined ) {

				const value = source[ property ];

				if ( this[ property ] && this[ property ].copy !== undefined ) {

					this[ property ].copy( value );

				} else {

					this[ property ] = value;

				}

			}

		}

		this.clippingPlanes = source.clippingPlanes ? source.clippingPlanes.map( ( plane ) => plane.clone() ) : null;

		this.userData = JSON.parse( JSON.stringify( source.userData ) );

		return this;

	}

}

const _camera = /*@__PURE__*/ new OrthographicCamera( -1, 1, 1, -1, 0, 1 );

/**
 * The purpose of this special geometry is to fill the entire viewport with a single triangle.
 *
 * Reference: {@link https://github.com/mrdoob/three.js/pull/21358}
 *
 * @private
 * @augments BufferGeometry
 */
class QuadGeometry extends BufferGeometry {

	/**
	 * Constructs a new quad geometry.
	 *
	 * @param {boolean} [flipY=false] - Whether the uv coordinates should be flipped along the vertical axis or not.
	 */
	constructor( flipY = false ) {

		super();

		const uv = flipY === false ? [ 0, -1, 0, 1, 2, 1 ] : [ 0, 2, 0, 0, 2, 0 ];

		this.setAttribute( 'position', new Float32BufferAttribute( [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ], 3 ) );
		this.setAttribute( 'uv', new Float32BufferAttribute( uv, 2 ) );

	}

}

const _geometry = /*@__PURE__*/ new QuadGeometry();

const _vertexNode = /*@__PURE__*/ vec4(
	array( [ -1, -1, 3.0 ] ).element( vertexIndex ),
	array( [ 3.0, -1, -1 ] ).element( vertexIndex ),
	0.0,
	1.0
);

/**
 * This module is a helper for passes which need to render a full
 * screen effect which is quite common in context of post processing.
 *
 * The intended usage is to reuse a single quad mesh for rendering
 * subsequent passes by just reassigning the `material` reference.
 *
 * Note: This module can only be used with `WebGPURenderer`.
 *
 * @augments Mesh
 */
class QuadMesh extends Mesh {

	/**
	 * Constructs a new quad mesh.
	 *
	 * @param {NodeMaterial} material - The material to render the quad mesh with.
	 */
	constructor( material ) {

		super( _geometry, material );

		/**
		 * The camera to render the quad mesh with.
		 *
		 * @type {OrthographicCamera}
		 * @readonly
		 */
		this.camera = _camera;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isQuadMesh = true;

	}

	/**
	 * Async version of `render()`.
	 *
	 * @async
	 * @deprecated
	 * @param {Renderer} renderer - The renderer.
	 * @return {Promise} A Promise that resolves when the render has been finished.
	 */
	async renderAsync( renderer ) {

		warnOnce( 'QuadMesh: "renderAsync()" has been deprecated. Use "render()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181

		await renderer.init();

		renderer.render( this, _camera );

	}

	/**
	 * Renders the quad mesh
	 *
	 * @param {Renderer} renderer - The renderer.
	 */
	render( renderer ) {

		const previousVertexNode = this.material.vertexNode;

		this.material.vertexNode = _vertexNode;

		renderer.render( this, _camera );

		this.material.vertexNode = previousVertexNode;

	}

}

/**
 * Saves the state of the given renderer and stores it into the given state object.
 *
 * If not state object is provided, the function creates one.
 *
 * @private
 * @function
 * @param {Renderer} renderer - The renderer.
 * @param {Object} [state={}] - The state.
 * @return {Object} The state.
 */
function saveRendererState( renderer, state = {} ) {

	state.toneMapping = renderer.toneMapping;
	state.toneMappingExposure = renderer.toneMappingExposure;
	state.outputColorSpace = renderer.outputColorSpace;
	state.renderTarget = renderer.getRenderTarget();
	state.activeCubeFace = renderer.getActiveCubeFace();
	state.activeMipmapLevel = renderer.getActiveMipmapLevel();
	state.renderObjectFunction = renderer.getRenderObjectFunction();
	state.pixelRatio = renderer.getPixelRatio();
	state.mrt = renderer.getMRT();
	state.clearColor = renderer.getClearColor( state.clearColor || new Color() );
	state.clearAlpha = renderer.getClearAlpha();
	state.autoClear = renderer.autoClear;
	state.autoClearColor = renderer.autoClearColor;
	state.autoClearDepth = renderer.autoClearDepth;
	state.autoClearStencil = renderer.autoClearStencil;
	state.opaque = renderer.opaque;
	state.transparent = renderer.transparent;
	state.scissorTest = renderer.getScissorTest();

	return state;

}

/**
 * Saves the state of the given renderer and stores it into the given state object.
 * Besides, the function also resets the state of the renderer to its default values.
 *
 * If not state object is provided, the function creates one.
 *
 * @private
 * @function
 * @param {Renderer} renderer - The renderer.
 * @param {Object} [state={}] - The state.
 * @return {Object} The state.
 */
function resetRendererState( renderer, state ) {

	state = saveRendererState( renderer, state );

	renderer.setMRT( null );
	renderer.setRenderObjectFunction( null );
	renderer.setClearColor( 0x000000, 1 );
	renderer.autoClear = true;
	renderer.autoClearColor = true;
	renderer.autoClearDepth = true;
	renderer.autoClearStencil = true;
	renderer.opaque = true;
	renderer.transparent = true;

	return state;

}

/**
 * Restores the state of the given renderer from the given state object.
 *
 * @private
 * @function
 * @param {Renderer} renderer - The renderer.
 * @param {Object} state - The state to restore.
 */
function restoreRendererState( renderer, state ) {

	renderer.toneMapping = state.toneMapping;
	renderer.toneMappingExposure = state.toneMappingExposure;
	renderer.outputColorSpace = state.outputColorSpace;
	renderer.setRenderTarget( state.renderTarget, state.activeCubeFace, state.activeMipmapLevel );
	renderer.setRenderObjectFunction( state.renderObjectFunction );
	renderer.setPixelRatio( state.pixelRatio );
	renderer.setMRT( state.mrt );
	renderer.setClearColor( state.clearColor, state.clearAlpha );
	renderer.autoClear = state.autoClear;
	renderer.autoClearColor = state.autoClearColor;
	renderer.autoClearDepth = state.autoClearDepth;
	renderer.autoClearStencil = state.autoClearStencil;
	renderer.opaque = state.opaque;
	renderer.transparent = state.transparent;
	renderer.setScissorTest( state.scissorTest );

}

/**
 * Saves the state of the given scene and stores it into the given state object.
 *
 * If not state object is provided, the function creates one.
 *
 * @private
 * @function
 * @param {Scene} scene - The scene.
 * @param {Object} [state={}] - The state.
 * @return {Object} The state.
 */
function saveSceneState( scene, state = {} ) {

	state.background = scene.background;
	state.backgroundNode = scene.backgroundNode;
	state.overrideMaterial = scene.overrideMaterial;

	return state;

}

/**
 * Saves the state of the given scene and stores it into the given state object.
 * Besides, the function also resets the state of the scene to its default values.
 *
 * If not state object is provided, the function creates one.
 *
 * @private
 * @function
 * @param {Scene} scene - The scene.
 * @param {Object} [state={}] - The state.
 * @return {Object} The state.
 */
function resetSceneState( scene, state ) {

	state = saveSceneState( scene, state );

	scene.background = null;
	scene.backgroundNode = null;
	scene.overrideMaterial = null;

	return state;

}

/**
 * Restores the state of the given scene from the given state object.
 *
 * @private
 * @function
 * @param {Scene} scene - The scene.
 * @param {Object} state - The state to restore.
 */
function restoreSceneState( scene, state ) {

	scene.background = state.background;
	scene.backgroundNode = state.backgroundNode;
	scene.overrideMaterial = state.overrideMaterial;

}

/**
 * Saves the state of the given renderer and scene and stores it into the given state object.
 *
 * If not state object is provided, the function creates one.
 *
 * @private
 * @function
 * @param {Renderer} renderer - The renderer.
 * @param {Scene} scene - The scene.
 * @param {Object} [state={}] - The state.
 * @return {Object} The state.
 */
function saveRendererAndSceneState( renderer, scene, state = {} ) {

	state = saveRendererState( renderer, state );
	state = saveSceneState( scene, state );

	return state;

}

/**
 * Saves the state of the given renderer and scene and stores it into the given state object.
 * Besides, the function also resets the state of the renderer and scene to its default values.
 *
 * If not state object is provided, the function creates one.
 *
 * @private
 * @function
 * @param {Renderer} renderer - The renderer.
 * @param {Scene} scene - The scene.
 * @param {Object} [state={}] - The state.
 * @return {Object} The state.
 */
function resetRendererAndSceneState( renderer, scene, state ) {

	state = resetRendererState( renderer, state );
	state = resetSceneState( scene, state );

	return state;

}

/**
 * Restores the state of the given renderer and scene from the given state object.
 *
 * @private
 * @function
 * @param {Renderer} renderer - The renderer.
 * @param {Scene} scene - The scene.
 * @param {Object} state - The state to restore.
 */
function restoreRendererAndSceneState( renderer, scene, state ) {

	restoreRendererState( renderer, state );
	restoreSceneState( scene, state );

}

var RendererUtils = /*#__PURE__*/Object.freeze({
	__proto__: null,
	resetRendererAndSceneState: resetRendererAndSceneState,
	resetRendererState: resetRendererState,
	resetSceneState: resetSceneState,
	restoreRendererAndSceneState: restoreRendererAndSceneState,
	restoreRendererState: restoreRendererState,
	restoreSceneState: restoreSceneState,
	saveRendererAndSceneState: saveRendererAndSceneState,
	saveRendererState: saveRendererState,
	saveSceneState: saveSceneState
});

const _size$1 = /*@__PURE__*/ new Vector2();

/**
 * `RTTNode` takes another node and uses it with a `QuadMesh` to render into a texture (RTT).
 * This module is especially relevant in context of post processing where certain nodes require
 * texture input for their effects. With the helper function `convertToTexture()` which is based
 * on this module, the node system can automatically ensure texture input if required.
 *
 * @augments TextureNode
 */
class RTTNode extends TextureNode {

	static get type() {

		return 'RTTNode';

	}

	/**
	 * Constructs a new RTT node.
	 *
	 * @param {Node} node - The node to render a texture with.
	 * @param {?number} [width=null] - The width of the internal render target. If no width is applied, the render target is automatically resized.
	 * @param {?number} [height=null] - The height of the internal render target.
	 * @param {Object} [options={}] - The options for the internal render target.
	 * @param {number} [options.type=HalfFloatType] - The texture type.
	 * @param {boolean} [options.autoUpdate=true] - Whether the texture should automatically be updated or not.
	 * @param {number} [options.resolutionScale=1] - The resolution scale.
	 */
	constructor( node, width = null, height = null, options = {} ) {

		const {
			autoUpdate = true,
			resolutionScale = 1
		} = options;

		const renderTarget = new RenderTarget( width ?? 1, height ?? 1, { type: HalfFloatType, ...options } );

		super( renderTarget.texture, uv$1() );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isRTTNode = true;

		/**
		 * The node to render a texture with.
		 *
		 * @type {Node}
		 */
		this.node = node;

		/**
		 * The width of the internal render target.
		 * If not width is applied, the render target is automatically resized.
		 *
		 * @type {?number}
		 * @default null
		 */
		this.width = width;

		/**
		 * The height of the internal render target.
		 *
		 * @type {?number}
		 * @default null
		 */
		this.height = height;

		/**
		 * The render target
		 *
		 * @type {RenderTarget}
		 */
		this.renderTarget = renderTarget;

		/**
		 * Whether the texture requires an update or not.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.textureNeedsUpdate = true;

		/**
		 * Whether the texture should automatically be updated or not.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.autoUpdate = autoUpdate;

		/**
		 * The resolution scale
		 *
		 * @private
		 * @type {number}
		 * @default 1
		 */
		this._resolutionScale = resolutionScale;

		/**
		 * The internal quad mesh for RTT.
		 *
		 * @private
		 * @type {QuadMesh}
		 */
		this._quadMesh = new QuadMesh( new NodeMaterial() );

		/**
		 * The renderer state saved and restored around the RTT render.
		 * Kept per instance because nested RTT nodes must not share it.
		 *
		 * @private
		 * @type {Object}
		 */
		this._rendererState = {};

		/**
		 * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates
		 * the texture once per frame in its {@link RTTNode#updateBefore} method.
		 *
		 * @type {string}
		 * @default 'frame'
		 */
		this.updateBeforeType = NodeUpdateType.FRAME;

	}

	/**
	 * Whether the internal render target should automatically be resized or not.
	 *
	 * @type {boolean}
	 * @readonly
	 * @default true
	 */
	get autoResize() {

		return this.width === null;

	}

	setup( builder ) {

		this._quadMesh.material.contextNode = context( builder.getSharedContext() );
		this._quadMesh.material.fragmentNode = this.node;
		this._quadMesh.material.name = 'RTT';
		this._quadMesh.material.needsUpdate = true;

		return super.setup( builder );

	}

	/**
	 * Sets the size of the internal render target.
	 *
	 * @param {number} width - The width to set.
	 * @param {number} height - The height to set.
	 */
	setSize( width, height ) {

		const effectiveWidth = Math.floor( width * this._resolutionScale );
		const effectiveHeight = Math.floor( height * this._resolutionScale );

		this.renderTarget.setSize( effectiveWidth, effectiveHeight );

		this.textureNeedsUpdate = true;

	}

	/**
	 * Sets the resolution scale.
	 * The resolution scale is a factor that is multiplied with the renderer's width and height.
	 *
	 * @param {number} resolutionScale - The resolution scale to set. A value of `1` means full resolution.
	 * @returns {RTTNode} A reference to this node.
	 */
	setResolutionScale( resolutionScale ) {

		this._resolutionScale = resolutionScale;

		if ( this.autoResize === false ) {

			this.setSize( this.width, this.height );

		}

		return this;

	}

	/**
	 * Gets the resolution scale.
	 *
	 * @returns {number} The resolution scale.
	 */
	getResolutionScale() {

		return this._resolutionScale;

	}

	/**
	 * Overwritten since the value is defined by the internal render target.
	 *
	 * @param {Texture} value - The texture value.
	 */
	set value( value ) {

		if ( this.renderTarget && value !== this.renderTarget.texture ) {

			error( 'TSL: "rtt()" does not allow overwriting the value.' );

		}

	}

	/**
	 * The texture of the internal render target.
	 *
	 * @type {Texture}
	 */
	get value() {

		return this.renderTarget ? this.renderTarget.texture : null;

	}

	/**
	 * Renders the node's output into the internal render target before the main render pass.
	 * Handles automatic resizing of the render target when `autoResize` is enabled,
	 * and skips rendering if neither `textureNeedsUpdate` nor `autoUpdate` is true.
	 *
	 * @param {NodeFrame} frame - The current node frame, providing access to the renderer and other frame data.
	 */
	updateBefore( frame ) {

		const { renderer } = frame;

		if ( this.textureNeedsUpdate === false && this.autoUpdate === false ) return;

		this.textureNeedsUpdate = false;

		//

		if ( this.autoResize === true ) {

			const size = renderer.getDrawingBufferSize( _size$1 );

			const effectiveWidth = Math.floor( size.width * this._resolutionScale );
			const effectiveHeight = Math.floor( size.height * this._resolutionScale );

			if ( effectiveWidth !== this.renderTarget.width || effectiveHeight !== this.renderTarget.height ) {

				this.renderTarget.setSize( effectiveWidth, effectiveHeight );

				this.textureNeedsUpdate = true;

			}

		}

		//

		let name = 'RTT';

		const callName = this.name || this.node.name;

		if ( callName ) {

			name = callName + ' [ ' + name + ' ]';

		}

		this._quadMesh.name = name;

		//

		resetRendererState( renderer, this._rendererState );

		renderer.setClearColor( this._rendererState.clearColor, this._rendererState.clearAlpha ); // keep clear color/alpha

		renderer.setRenderTarget( this.renderTarget );

		this._quadMesh.render( renderer );

		restoreRendererState( renderer, this._rendererState );

	}

	clone() {

		const newNode = new TextureNode( this.value, this.uvNode, this.levelNode );
		newNode.sampler = this.sampler;
		newNode.referenceNode = this;

		return newNode;

	}

	/**
	 * Frees internal resources. Should be called when the node is no longer in use.
	 */
	dispose() {

		this.renderTarget.dispose();
		this._quadMesh.material.dispose();

		super.dispose();

	}

}

/**
 * TSL function for creating a RTT node.
 *
 * @tsl
 * @function
 * @param {Node} node - The node to render a texture with.
 * @param {?number} [width=null] - The width of the internal render target. If no width is applied, the render target is automatically resized.
 * @param {?number} [height=null] - The height of the internal render target.
 * @param {Object} [options={}] - The options for the internal render target.
 * @param {number} [options.type=HalfFloatType] - The texture type.
 * @param {boolean} [options.autoUpdate=true] - Whether the texture should automatically be updated or not.
 * @param {number} [options.resolutionScale=1] - The resolution scale.
 * @returns {RTTNode}
 */
const rtt = ( node, ...params ) => new RTTNode( nodeObject( node ), ...params );

/**
 * TSL function for converting nodes to textures nodes.
 *
 * @tsl
 * @function
 * @param {Node} node - The node to render a texture with.
 * @param {?number} [width=null] - The width of the internal render target. If no width is applied, the render target is automatically resized.
 * @param {?number} [height=null] - The height of the internal render target.
 * @param {Object} [options={}] - The options for the internal render target.
 * @param {number} [options.type=HalfFloatType] - The texture type.
 * @param {boolean} [options.autoUpdate=true] - Whether the texture should automatically be updated or not.
 * @param {number} [options.resolutionScale=1] - The resolution scale.
 * @returns {RTTNode}
 */
const convertToTexture = ( node, ...params ) => {

	if ( node.isSampleNode || node.isTextureNode ) return node;
	if ( node.isPassNode ) return node.getTextureNode();

	return rtt( node, ...params );

};

/**
 * Returns `true` if the given depth value belongs to the background, i.e. the cleared
 * far plane. Takes the renderer's reversed depth buffer into account.
 *
 * @tsl
 * @function
 * @param {Node<float>} depth - The depth value.
 * @return {Node<bool>} Whether the depth value belongs to the background.
 */
const isBackgroundDepth = /*@__PURE__*/ Fn( ( [ depth ], builder ) => {

	return builder.renderer.reversedDepthBuffer === true ? depth.lessThanEqual( 0.0 ) : depth.greaterThanEqual( 1.0 );

} );

/**
 * Computes a position in view space based on a fragment's screen position expressed as uv coordinates, the fragments
 * depth value and the camera's inverse projection matrix.
 *
 * @tsl
 * @function
 * @param {Node<vec2>} screenPosition - The fragment's screen position expressed as uv coordinates.
 * @param {Node<float>} depth - The fragment's depth value.
 * @param {Node<mat4>} projectionMatrixInverse - The camera's inverse projection matrix.
 * @return {Node<vec3>} The fragments position in view space.
 */
const getViewPosition = /*@__PURE__*/ Fn( ( [ screenPosition, depth, projectionMatrixInverse ], builder ) => {

	let clipSpacePosition;

	if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem || builder.renderer.reversedDepthBuffer === true ) {

		screenPosition = vec2( screenPosition.x, screenPosition.y.oneMinus() ).mul( 2.0 ).sub( 1.0 );
		clipSpacePosition = vec4( vec3( screenPosition, depth ), 1.0 );

	} else {

		clipSpacePosition = vec4( vec3( screenPosition.x, screenPosition.y.oneMinus(), depth ).mul( 2.0 ).sub( 1.0 ), 1.0 );

	}

	const viewSpacePosition = vec4( projectionMatrixInverse.mul( clipSpacePosition ) );

	return viewSpacePosition.xyz.div( viewSpacePosition.w );

} );

/**
 * Computes a screen position expressed as uv coordinates based on a fragment's position in view space
 * and the camera's projection matrix
 *
 * @tsl
 * @function
 * @param {Node<vec3>} viewPosition - The fragments position in view space.
 * @param {Node<mat4>} projectionMatrix - The camera's projection matrix.
 * @return {Node<vec2>} The fragment's screen position expressed as uv coordinates.
 */
const getScreenPosition = /*@__PURE__*/ Fn( ( [ viewPosition, projectionMatrix ] ) => {

	const sampleClipPos = projectionMatrix.mul( vec4( viewPosition, 1.0 ) );
	const sampleUv = sampleClipPos.xy.div( sampleClipPos.w ).mul( 0.5 ).add( 0.5 ).toVar();
	return vec2( sampleUv.x, sampleUv.y.oneMinus() );

}, { viewPosition: 'vec3', projectionMatrix: 'mat4', return: 'vec2' } );

/**
 * Converts a clip-space position into a screen position expressed as uv coordinates.
 *
 * @tsl
 * @function
 * @param {Node<vec4>} clipPosition - The position in clip space.
 * @return {Node<vec2>} The screen position expressed as uv coordinates.
 */
const getScreenPositionFromClip = /*@__PURE__*/ Fn( ( [ clipPosition ] ) => {

	const screen = clipPosition.xy.div( clipPosition.w ).mul( 0.5 ).add( 0.5 ).toVar();
	return vec2( screen.x, screen.y.oneMinus() );

}, { clipPosition: 'vec4', return: 'vec2' } );

/**
 * Computes a normal vector based on depth data. Can be used as a fallback when no normal render
 * target is available or if flat surface normals are required.
 *
 * @tsl
 * @function
 * @param {Node<vec2>} uv - The texture coordinate.
 * @param {DepthTexture} depthTexture - The depth texture.
 * @param {Node<mat4>} projectionMatrixInverse - The camera's inverse projection matrix.
 * @return {Node<vec3>} The computed normal vector.
 */
const getNormalFromDepth = /*@__PURE__*/ Fn( ( [ uv, depthTexture, projectionMatrixInverse ] ) => {

	const size = textureSize( textureLoad( depthTexture ) );
	const p = ivec2( uv.mul( size ) ).toVar();

	const c0 = textureLoad( depthTexture, p ).toVar();

	const l2 = textureLoad( depthTexture, p.sub( ivec2( 2, 0 ) ) ).toVar();
	const l1 = textureLoad( depthTexture, p.sub( ivec2( 1, 0 ) ) ).toVar();
	const r1 = textureLoad( depthTexture, p.add( ivec2( 1, 0 ) ) ).toVar();
	const r2 = textureLoad( depthTexture, p.add( ivec2( 2, 0 ) ) ).toVar();
	const b2 = textureLoad( depthTexture, p.add( ivec2( 0, 2 ) ) ).toVar();
	const b1 = textureLoad( depthTexture, p.add( ivec2( 0, 1 ) ) ).toVar();
	const t1 = textureLoad( depthTexture, p.sub( ivec2( 0, 1 ) ) ).toVar();
	const t2 = textureLoad( depthTexture, p.sub( ivec2( 0, 2 ) ) ).toVar();

	const dl = abs( sub( float( 2 ).mul( l1 ).sub( l2 ), c0 ) ).toVar();
	const dr = abs( sub( float( 2 ).mul( r1 ).sub( r2 ), c0 ) ).toVar();
	const db = abs( sub( float( 2 ).mul( b1 ).sub( b2 ), c0 ) ).toVar();
	const dt = abs( sub( float( 2 ).mul( t1 ).sub( t2 ), c0 ) ).toVar();

	const ce = getViewPosition( uv, c0, projectionMatrixInverse ).toVar();

	const dpdx = dl.lessThan( dr ).select( ce.sub( getViewPosition( uv.sub( vec2( float( 1 ).div( size.x ), 0 ) ), l1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.add( vec2( float( 1 ).div( size.x ), 0 ) ), r1, projectionMatrixInverse ) ) );
	const dpdy = db.lessThan( dt ).select( ce.sub( getViewPosition( uv.add( vec2( 0, float( 1 ).div( size.y ) ) ), b1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.sub( vec2( 0, float( 1 ).div( size.y ) ) ), t1, projectionMatrixInverse ) ) );

	return normalize( cross( dpdx, dpdy ) );

} );

/**
 * Interleaved Gradient Noise (IGN) from Jimenez 2014.
 *
 * IGN has "low discrepancy" resulting in evenly distributed samples. It's superior compared to
 * default white noise, blue noise or Bayer.
 *
 * References:
 * - {@link https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/}
 * - {@link https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence/}
 *
 * @tsl
 * @function
 * @param {Node<vec2>} position - The input position, usually screen coordinates.
 * @return {Node<float>} The noise value.
 */
const interleavedGradientNoise = /*@__PURE__*/ Fn( ( [ position ] ) => {

	return fract( float( 52.9829189 ).mul( fract( dot( position, vec2( 0.06711056, 0.00583715 ) ) ) ) );

}, { position: 'vec2', return: 'float' } );

/**
 * Vogel disk sampling for uniform circular distribution.
 *
 * This function generates sample points distributed uniformly on a disk using the golden angle,
 * resulting in an efficient low-discrepancy sequence for sampling. The rotation parameter (phi)
 * allows randomizing the pattern per-pixel when combined with IGN.
 *
 * @tsl
 * @function
 * @param {Node<int>} sampleIndex - The index of the current sample (0-based).
 * @param {Node<int>} samplesCount - The total number of samples.
 * @param {Node<float>} phi - Rotation angle in radians (typically from IGN * 2π).
 * @return {Node<vec2>} A 2D point on the unit disk.
 */
const vogelDiskSample = /*@__PURE__*/ Fn( ( [ sampleIndex, samplesCount, phi ] ) => {

	const goldenAngle = float( 2.399963229728653 ); // 2π * (2 - φ) where φ is golden ratio
	const r = sqrt( float( sampleIndex ).add( 0.5 ).div( float( samplesCount ) ) );
	const theta = float( sampleIndex ).mul( goldenAngle ).add( phi );
	return vec2( cos( theta ), sin( theta ) ).mul( r );

}, { sampleIndex: 'int', samplesCount: 'int', phi: 'float', return: 'vec2' } );

/**
 * Class representing a node that samples a value using a provided callback function.
 *
 * @extends Node
 */
class SampleNode extends Node {

	/**
	 * Returns the type of the node.
	 *
	 * @type {string}
	 * @readonly
	 * @static
	 */
	static get type() {

		return 'SampleNode';

	}

	/**
	 * Creates an instance of SampleNode.
	 *
	 * @param {Function} callback - The function to be called when sampling. Should accept a UV node and return a value.
	 * @param {?Node<vec2>} [uvNode=null] - The UV node to be used in the texture sampling.
	 */
	constructor( callback, uvNode = null ) {

		super();

		this.callback = callback;

		/**
		 * Represents the texture coordinates.
		 *
		 * @type {?Node<vec2|vec3>}
		 * @default null
		 */
		this.uvNode = uvNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isSampleNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Sets up the node by sampling with the default UV accessor.
	 *
	 * @returns {Node} The result of the callback function when called with the UV node.
	 */
	setup() {

		return this.sample( uv$1() );

	}

	/**
	 * Calls the callback function with the provided UV node.
	 *
	 * @param {Node<vec2>} uv - The UV node or value to be passed to the callback.
	 * @returns {Node} The result of the callback function.
	 */
	sample( uv ) {

		return this.callback( uv );

	}

}

/**
 * Helper function to create a SampleNode wrapped as a node object.
 *
 * @function
 * @param {Function} callback - The function to be called when sampling. Should accept a UV node and return a value.
 * @param {?Node<vec2>} [uv=null] - The UV node to be used in the texture sampling.
 * @returns {SampleNode} The created SampleNode instance wrapped as a node object.
 */
const sample = ( callback, uv = null ) => new SampleNode( callback, nodeObject( uv ) );

/**
 * This special type of instanced buffer attribute is intended for compute shaders.
 * In earlier three.js versions it was only possible to update attribute data
 * on the CPU via JavaScript and then upload the data to the GPU. With the
 * new material system and renderer it is now possible to use compute shaders
 * to compute the data for an attribute more efficiently on the GPU.
 *
 * The idea is to create an instance of this class and provide it as an input
 * to {@link StorageBufferNode}.
 *
 * Note: This type of buffer attribute can only be used with `WebGPURenderer`.
 *
 * @augments InstancedBufferAttribute
 */
class StorageInstancedBufferAttribute extends InstancedBufferAttribute {

	/**
	 * Constructs a new storage instanced buffer attribute.
	 *
	 * @param {number|TypedArray} count - The item count. It is also valid to pass a typed array as an argument.
	 * The subsequent parameters are then obsolete.
	 * @param {number} itemSize - The item size.
	 * @param {TypedArray.constructor} [typeClass=Float32Array] - A typed array constructor.
	 */
	constructor( count, itemSize, typeClass = Float32Array ) {

		const array = ArrayBuffer.isView( count ) ? count : new typeClass( count * itemSize );

		super( array, itemSize );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isStorageInstancedBufferAttribute = true;

	}

}

/**
 * This special type of buffer attribute is intended for compute shaders.
 * In earlier three.js versions it was only possible to update attribute data
 * on the CPU via JavaScript and then upload the data to the GPU. With the
 * new material system and renderer it is now possible to use compute shaders
 * to compute the data for an attribute more efficiently on the GPU.
 *
 * The idea is to create an instance of this class and provide it as an input
 * to {@link StorageBufferNode}.
 *
 * Note: This type of buffer attribute can only be used with `WebGPURenderer`.
 *
 * @augments BufferAttribute
 */
class StorageBufferAttribute extends BufferAttribute {

	/**
	 * Constructs a new storage buffer attribute.
	 *
	 * @param {number|TypedArray} count - The item count. It is also valid to pass a typed array as an argument.
	 * The subsequent parameters are then obsolete.
	 * @param {number} itemSize - The item size.
	 * @param {TypedArray.constructor} [typeClass=Float32Array] - A typed array constructor.
	 */
	constructor( count, itemSize, typeClass = Float32Array ) {

		const array = ArrayBuffer.isView( count ) ? count : new typeClass( count * itemSize );

		super( array, itemSize );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isStorageBufferAttribute = true;

	}

}

/**
 * TSL function for creating a storage buffer node with a configured `StorageBufferAttribute`.
 *
 * @tsl
 * @function
 * @param {number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument.
 * @param {string|Struct} [type='float'] - The data type.
 * @returns {StorageBufferNode}
 */
const attributeArray = ( count, type = 'float' ) => {

	let itemSize, typedArray;

	if ( type.isStructTypeNode === true ) {

		itemSize = type.getLength();
		typedArray = getTypedArrayFromType( 'float' );

	} else {

		itemSize = getLengthFromType( type );
		typedArray = getTypedArrayFromType( type );

	}

	const buffer = new StorageBufferAttribute( count, itemSize, typedArray );
	const node = storage( buffer, type, buffer.count );

	return node;

};

/**
 * TSL function for creating a storage buffer node with a configured `StorageInstancedBufferAttribute`.
 *
 * @tsl
 * @function
 * @param {number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument.
 * @param {string|Struct} [type='float'] - The data type.
 * @returns {StorageBufferNode}
 */
const instancedArray = ( count, type = 'float' ) => {

	let itemSize, typedArray;

	if ( type.isStructTypeNode === true ) {

		itemSize = type.getLength();
		typedArray = getTypedArrayFromType( 'float' );

	} else {

		itemSize = getLengthFromType( type );
		typedArray = getTypedArrayFromType( type );

	}

	const buffer = new StorageInstancedBufferAttribute( count, itemSize, typedArray );
	const node = storage( buffer, type, buffer.count );

	return node;

};

// Mipped Bicubic Texture Filtering by N8
// https://www.shadertoy.com/view/Dl2SDW

const bC = 1.0 / 6.0;

const w0 = ( a ) => mul( bC, mul( a, mul( a, a.negate().add( 3.0 ) ).sub( 3.0 ) ).add( 1.0 ) );

const w1 = ( a ) => mul( bC, mul( a, mul( a, mul( 3.0, a ).sub( 6.0 ) ) ).add( 4.0 ) );

const w2 = ( a ) => mul( bC, mul( a, mul( a, mul( -3, a ).add( 3.0 ) ).add( 3.0 ) ).add( 1.0 ) );

const w3 = ( a ) => mul( bC, pow( a, 3 ) );

const bicubicWeights = ( a ) => {

	const w0a = w0( a );
	const w1a = w1( a );
	const w2a = w2( a );
	const w3a = w3( a );

	const g0a = w0a.add( w1a );
	const g1a = w2a.add( w3a );

	// h0 and h1 are the two offset functions.
	const h0a = add( -1, w1a.div( g0a ) );
	const h1a = add( 1.0, w3a.div( g1a ) );

	return { g0: g0a, g1: g1a, h0: h0a, h1: h1a };

};

const bicubic = ( textureNode, p0, p3, g0, g1, lod ) => {

	const p1 = vec2( p3.x, p0.y );
	const p2 = vec2( p0.x, p3.y );

	const a = g0.y.mul( add( g0.x.mul( textureNode.sample( p0 ).level( lod ) ), g1.x.mul( textureNode.sample( p1 ).level( lod ) ) ) );
	const b = g1.y.mul( add( g0.x.mul( textureNode.sample( p2 ).level( lod ) ), g1.x.mul( textureNode.sample( p3 ).level( lod ) ) ) );

	return a.add( b );

};

/**
 * Applies mipped bicubic texture filtering to the given texture node.
 *
 * @tsl
 * @function
 * @param {TextureNode} textureNode - The texture node that should be filtered.
 * @param {Node<float>} lodNode - Defines the LOD to sample from.
 * @return {Node} The filtered texture sample.
 */
const textureBicubicLevel = /*@__PURE__*/ Fn( ( [ textureNode, lodNode ] ) => {

	const fLodSize = vec2( textureNode.size( int( lodNode ) ) );
	const cLodSize = vec2( textureNode.size( int( lodNode.add( 1.0 ) ) ) );
	const lodSize = vec4( fLodSize, cLodSize );
	const lodSizeInv = div( 1.0, lodSize );
	const uvScaled = textureNode.uvNode.xyxy.mul( lodSize ).add( 0.5 );
	const iuv = floor( uvScaled );
	const fuv = fract( uvScaled );

	const { g0, g1, h0, h1 } = bicubicWeights( fuv );

	const p0 = iuv.add( h0 ).sub( 0.5 ).mul( lodSizeInv );
	const p3 = iuv.add( h1 ).sub( 0.5 ).mul( lodSizeInv );

	const fSample = bicubic( textureNode, p0.xy, p3.xy, g0.xy, g1.xy, floor( lodNode ) );
	const cSample = bicubic( textureNode, p0.zw, p3.zw, g0.zw, g1.zw, ceil( lodNode ) );

	return fract( lodNode ).mix( fSample, cSample );

} );

/**
 * Applies mipped bicubic texture filtering to the given texture node.
 *
 * @tsl
 * @function
 * @param {TextureNode} textureNode - The texture node that should be filtered.
 * @param {Node<float>} [strength] - Defines the strength of the bicubic filtering.
 * @return {Node} The filtered texture sample.
 */
const textureBicubic = /*@__PURE__*/ Fn( ( [ textureNode, strength ] ) => {

	const lod = strength.mul( maxMipLevel( textureNode ) );

	return textureBicubicLevel( textureNode, lod );

} );

/**
 * A node for representing the uv coordinates of points.
 *
 * Can only be used with a WebGL backend. In WebGPU, point
 * primitives always have the size of one pixel and can thus
 * can't be used as sprite-like objects that display textures.
 *
 * @augments Node
 */
class PointUVNode extends Node {

	static get type() {

		return 'PointUVNode';

	}

	/**
	 * Constructs a new point uv node.
	 */
	constructor() {

		super( 'vec2' );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isPointUVNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	generate( /*builder*/ ) {

		return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )';

	}

}

/**
 * TSL object that represents the uv coordinates of points.
 *
 * @tsl
 * @type {PointUVNode}
 */
const pointUV = /*@__PURE__*/ nodeImmutable( PointUVNode );

const _m1 = /*@__PURE__*/ new Matrix4();

/**
 * TSL object that represents the scene's background blurriness.
 *
 * @tsl
 * @type {Node<float>}
 */
const backgroundBlurriness = /*@__PURE__*/ uniform( 0 ).setGroup( renderGroup ).onRenderUpdate( ( { scene } ) => scene.backgroundBlurriness );

/**
 * TSL object that represents the scene's background intensity.
 *
 * @tsl
 * @type {Node<float>}
 */
const backgroundIntensity = /*@__PURE__*/ uniform( 1 ).setGroup( renderGroup ).onRenderUpdate( ( { scene } ) => scene.backgroundIntensity );

/**
 * TSL object that represents the scene's background rotation.
 *
 * @tsl
 * @type {Node<mat4>}
 */
const backgroundRotation = /*@__PURE__*/ uniform( new Matrix4() ).setGroup( renderGroup ).onRenderUpdate( ( { scene } ) => {

	const background = scene.background;

	if ( ( background !== null && background.isTexture && background.mapping !== UVMapping ) || ( scene.backgroundNode && scene.backgroundNode.isNode ) ) {

		// note: since the matrix is orthonormal, we can use the more-efficient transpose() in lieu of invert()
		_m1.makeRotationFromEuler( scene.backgroundRotation ).transpose();

	} else {

		_m1.identity();

	}

	return _m1;

} );

/**
 * This special version of a texture node can be used to
 * write data into a storage texture with a compute shader.
 *
 * ```js
 * const storageTexture = new THREE.StorageTexture( width, height );
 *
 * const computeTexture = Fn( ( { storageTexture } ) => {
 *
 * 	const posX = instanceIndex.mod( width );
 * 	const posY = instanceIndex.div( width );
 * 	const indexUV = uvec2( posX, posY );
 *
 * 	// generate RGB values
 *
 * 	const r = 1;
 * 	const g = 1;
 * 	const b = 1;
 *
 * 	textureStore( storageTexture, indexUV, vec4( r, g, b, 1 ) ).toWriteOnly();
 *
 * } );
 *
 * const computeNode = computeTexture( { storageTexture } ).compute( width * height );
 * renderer.computeAsync( computeNode );
 * ```
 *
 * This node can only be used with a WebGPU backend.
 *
 * @augments TextureNode
 */
class StorageTextureNode extends TextureNode {

	static get type() {

		return 'StorageTextureNode';

	}

	/**
	 * Constructs a new storage texture node.
	 *
	 * @param {StorageTexture} value - The storage texture.
	 * @param {Node<vec2|vec3>} uvNode - The uv node.
	 * @param {?Node} [storeNode=null] - The value node that should be stored in the texture.
	 */
	constructor( value, uvNode, storeNode = null ) {

		super( value, uvNode );

		/**
		 * The value node that should be stored in the texture.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.storeNode = storeNode;

		/**
		 * The mip level to write to for storage textures.
		 *
		 * @type {number}
		 * @default 0
		 */
		this.mipLevel = 0;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isStorageTextureNode = true;

		/**
		 * The access type of the texture node.
		 *
		 * @type {string}
		 * @default 'writeOnly'
		 */
		this.access = NodeAccess.WRITE_ONLY;

	}

	/**
	 * Overwrites the default implementation to return a fixed value `'storageTexture'`.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( /*builder*/ ) {

		return 'storageTexture';

	}

	/**
	 * Overwrites the default implementation since storage texture
	 * coordinates are texel coordinates and should not be transformed
	 * by the texture uv matrix.
	 *
	 * @param {Node} uvNode - The uv node.
	 * @return {Node} The unmodified uv node.
	 */
	getTransformedUV( uvNode ) {

		return uvNode;

	}

	setup( builder ) {

		super.setup( builder );

		const properties = builder.getNodeProperties( this );
		properties.storeNode = this.storeNode;

		return properties;

	}

	/**
	 * Defines the node access.
	 *
	 * @param {string} value - The node access.
	 * @return {StorageTextureNode} A reference to this node.
	 */
	setAccess( value ) {

		this.access = value;
		return this;

	}

	/**
	 * Sets the mip level to write to.
	 *
	 * @param {number} level - The mip level.
	 * @return {StorageTextureNode} A reference to this node.
	 */
	setMipLevel( level ) {

		this.mipLevel = level;
		return this;

	}

	/**
	 * Generates the code snippet of the storage node. If no `storeNode`
	 * is defined, the texture node is generated as normal texture.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} output - The current output.
	 * @return {string} The generated code snippet.
	 */
	generate( builder, output ) {

		if ( this.storeNode !== null ) {

			this.generateStore( builder );
			return '';

		}

		return super.generate( builder, output );

	}

	/**
	 * Generates the snippet for the storage texture.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} textureProperty - The texture property.
	 * @param {string} uvSnippet - The uv snippet.
	 * @param {?string} levelSnippet - The level snippet.
	 * @param {?string} biasSnippet - The bias snippet.
	 * @param {?string} depthSnippet - The depth snippet.
	 * @param {?string} compareSnippet - The compare snippet.
	 * @param {?Array<string>} gradSnippet - The grad snippet.
	 * @param {?string} offsetSnippet - The offset snippet.
	 * @return {string} The generated code snippet.
	 */
	generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet, offsetSnippet ) {

		const texture = this.value;

		return builder.generateStorageTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet );

	}

	/**
	 * Convenience method for configuring a read/write node access.
	 *
	 * @return {StorageTextureNode} A reference to this node.
	 */
	toReadWrite() {

		return this.setAccess( NodeAccess.READ_WRITE );

	}

	/**
	 * Convenience method for configuring a read-only node access.
	 *
	 * @return {StorageTextureNode} A reference to this node.
	 */
	toReadOnly() {

		return this.setAccess( NodeAccess.READ_ONLY );

	}

	/**
	 * Convenience method for configuring a write-only node access.
	 *
	 * @return {StorageTextureNode} A reference to this node.
	 */
	toWriteOnly() {

		return this.setAccess( NodeAccess.WRITE_ONLY );

	}

	/**
	 * Stores a value in this storage texture at the given coordinates.
	 *
	 * @param {Node<vec2|vec3>} uvNode - The storage texture coordinates.
	 * @param {?Node} [storeNode=null] - The value node that should be stored in the texture.
	 * @return {StorageTextureNode} A storage texture node representing the store operation.
	 */
	store( uvNode, storeNode ) {

		const node = this.clone();

		node.referenceNode = this.getBase();
		node.uvNode = uvNode;
		node.storeNode = storeNode;

		if ( storeNode !== null ) node.toStack();

		return node;

	}

	/**
	 * Generates the code snippet of the storage texture node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	generateStore( builder ) {

		const properties = builder.getNodeProperties( this );

		const { uvNode, storeNode, depthNode } = properties;

		const textureProperty = super.generate( builder, 'property' );
		const uvSnippet = uvNode.build( builder, this.value.is3DTexture === true ? 'uvec3' : 'uvec2' );
		const storeType = builder.getTypeFromLength( 4, builder.getComponentTypeFromTexture( this.value ) );
		const storeSnippet = storeNode.build( builder, storeType );
		const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null;

		const snippet = builder.generateTextureStore( this.value, textureProperty, uvSnippet, depthSnippet, storeSnippet );

		builder.addLineFlowCode( snippet, this );

	}

	clone() {

		const newNode = super.clone();
		newNode.storeNode = this.storeNode;
		newNode.mipLevel = this.mipLevel;
		newNode.access = this.access;
		return newNode;

	}

}

/**
 * TSL function for creating a storage texture node.
 *
 * @tsl
 * @function
 * @param {StorageTexture} value - The storage texture.
 * @param {?Node<vec2|vec3>} uvNode - The uv node.
 * @param {?Node} [storeNode=null] - The value node that should be stored in the texture.
 * @returns {StorageTextureNode}
 */
const storageTexture = /*@__PURE__*/ nodeProxy( StorageTextureNode ).setParameterLength( 1, 3 );


/**
 * TSL function for storing a value in a storage texture.
 *
 * Unlike {@link storageTexture}, this function also accepts an existing storage
 * texture node and is intended for performing the store operation itself.
 *
 * @tsl
 * @function
 * @param {StorageTexture|StorageTextureNode} value - The storage texture or storage texture node.
 * @param {Node<vec2|vec3>} uvNode - The uv node.
 * @param {?Node} [storeNode=null] - The value node that should be stored in the texture.
 * @returns {StorageTextureNode}
 */
const textureStore = ( value, uvNode, storeNode ) => {

	let node;

	if ( value.isStorageTextureNode === true ) {

		node = value.store( uvNode, storeNode );

	} else {

		node = storageTexture( value, uvNode, storeNode );

		if ( storeNode !== null ) node.toStack();

	}

	return node;

};

/**
 * This special version of a texture node can be used to
 * write data into a 3D storage texture with a compute shader.
 *
 * @augments StorageTextureNode
 */
class StorageTexture3DNode extends StorageTextureNode {

	static get type() {

		return 'StorageTexture3DNode';

	}

	/**
	 * Constructs a new 3D storage texture node.
	 *
	 * @param {Storage3DTexture} value - The 3D storage texture.
	 * @param {Node<vec3>} uvNode - The uv node.
	 * @param {?Node} [storeNode=null] - The value node that should be stored in the texture.
	 */
	constructor( value, uvNode, storeNode = null ) {

		super( value, uvNode, storeNode );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isStorageTexture3DNode = true;

	}

	/**
	 * Returns a default uv node which is in context of 3D textures a three-dimensional
	 * uv node.
	 *
	 * @return {Node<vec3>} The default uv node.
	 */
	getDefaultUV() {

		return vec3( 0.5, 0.5, 0.5 );

	}

	/**
	 * Overwritten with an empty implementation since the `updateMatrix` flag is ignored
	 * for 3D textures. The uv transformation matrix is not applied to 3D textures.
	 *
	 * @param {boolean} value - The update toggle.
	 */
	setUpdateMatrix( /*value*/ ) { } // Ignore .updateMatrix for 3d TextureNode

	/**
	 * Generates the uv code snippet.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node} uvNode - The uv node to generate code for.
	 * @return {string} The generated code snippet.
	 */
	generateUV( builder, uvNode ) {

		return uvNode.build( builder, this.sampler === true ? 'vec3' : 'ivec3' );

	}

	/**
	 * Generates the offset code snippet.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node} offsetNode - The offset node to generate code for.
	 * @return {string} The generated code snippet.
	 */
	generateOffset( builder, offsetNode ) {

		return offsetNode.build( builder, 'ivec3' );

	}

}

/**
 * TSL function for creating a 3D storage texture node.
 *
 * @tsl
 * @function
 * @param {Storage3DTexture} value - The 3D storage texture.
 * @param {?Node<vec3>} [uvNode=null] - The uv node.
 * @param {?Node} [storeNode=null] - The value node that should be stored in the texture.
 * @returns {StorageTexture3DNode}
 */
const storageTexture3D = /*@__PURE__*/ nodeProxy( StorageTexture3DNode ).setParameterLength( 1, 3 );

const normal = /*@__PURE__*/ Fn( ( { texture, uv } ) => {

	const epsilon = 0.0001;

	const ret = vec3().toVar();

	If( uv.x.lessThan( epsilon ), () => {

		ret.assign( vec3( 1, 0, 0 ) );

	} ).ElseIf( uv.y.lessThan( epsilon ), () => {

		ret.assign( vec3( 0, 1, 0 ) );

	} ).ElseIf( uv.z.lessThan( epsilon ), () => {

		ret.assign( vec3( 0, 0, 1 ) );

	} ).ElseIf( uv.x.greaterThan( 1 - epsilon ), () => {

		ret.assign( vec3( -1, 0, 0 ) );

	} ).ElseIf( uv.y.greaterThan( 1 - epsilon ), () => {

		ret.assign( vec3( 0, -1, 0 ) );

	} ).ElseIf( uv.z.greaterThan( 1 - epsilon ), () => {

		ret.assign( vec3( 0, 0, -1 ) );

	} ).Else( () => {

		const step = 0.01;

		const x = texture.sample( uv.add( vec3( - step, 0.0, 0.0 ) ) ).r.sub( texture.sample( uv.add( vec3( step, 0.0, 0.0 ) ) ).r );
		const y = texture.sample( uv.add( vec3( 0.0, - step, 0.0 ) ) ).r.sub( texture.sample( uv.add( vec3( 0.0, step, 0.0 ) ) ).r );
		const z = texture.sample( uv.add( vec3( 0.0, 0.0, - step ) ) ).r.sub( texture.sample( uv.add( vec3( 0.0, 0.0, step ) ) ).r );

		ret.assign( vec3( x, y, z ) );

	} );

	return ret.normalize();

} );

/**
 * This type of uniform node represents a 3D texture.
 *
 * @augments TextureNode
 */
class Texture3DNode extends TextureNode {

	static get type() {

		return 'Texture3DNode';

	}

	/**
	 * Constructs a new 3D texture node.
	 *
	 * @param {Data3DTexture} value - The 3D texture.
	 * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
	 * @param {?Node<int>} [levelNode=null] - The level node.
	 */
	constructor( value, uvNode = null, levelNode = null ) {

		super( value, uvNode, levelNode );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isTexture3DNode = true;

	}

	/**
	 * Overwrites the default implementation to return a fixed value `'texture3D'`.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( /*builder*/ ) {

		return 'texture3D';

	}

	/**
	 * Returns a default uv node which is in context of 3D textures a three-dimensional
	 * uv node.
	 *
	 * @return {Node<vec3>} The default uv node.
	 */
	getDefaultUV() {

		return vec3( 0.5, 0.5, 0.5 );

	}

	/**
	 * Overwritten with an empty implementation since the `updateMatrix` flag is ignored
	 * for 3D textures. The uv transformation matrix is not applied to 3D textures.
	 *
	 * @param {boolean} value - The update toggle.
	 */
	setUpdateMatrix( /*value*/ ) { } // Ignore .updateMatrix for 3d TextureNode

	/**
	 * Generates the uv code snippet.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node} uvNode - The uv node to generate code for.
	 * @return {string} The generated code snippet.
	 */
	generateUV( builder, uvNode ) {

		return uvNode.build( builder, this.sampler === true ? 'vec3' : 'ivec3' );

	}

	/**
	 * Generates the offset code snippet.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {Node} offsetNode - The offset node to generate code for.
	 * @return {string} The generated code snippet.
	 */
	generateOffset( builder, offsetNode ) {

		return offsetNode.build( builder, 'ivec3' );

	}

	/**
	 * Computes the normal for the given uv. These texture coordiantes represent a
	 * position inside the 3D texture. Unlike geometric normals, this normal
	 * represents a slope or gradient of scalar data inside the 3D texture.
	 *
	 * @param {Node<vec3>} uvNode - The uv node that defines a position in the 3D texture.
	 * @return {Node<vec3>} The normal representing the slope/gradient in the data.
	 */
	normal( uvNode ) {

		return normal( { texture: this, uv: uvNode } );

	}

}

/**
 * TSL function for creating a 3D texture node.
 *
 * @tsl
 * @function
 * @param {Data3DTexture} value - The 3D texture.
 * @param {?Node<vec3>} [uvNode=null] - The uv node.
 * @param {?Node<int>} [levelNode=null] - The level node.
 * @returns {Texture3DNode}
 */
const texture3D = /*@__PURE__*/ nodeProxy( Texture3DNode ).setParameterLength( 1, 3 );

/**
 * TSL function for creating a texture node that fetches/loads texels without interpolation.
 *
 * @tsl
 * @function
 * @param {?(Texture|TextureNode)} [value=EmptyTexture] - The texture.
 * @param {?Node<vec3>} [uvNode=null] - The uv node.
 * @param {?Node<int>} [levelNode=null] - The level node.
 * @param {?Node<float>} [biasNode=null] - The bias node.
 * @returns {TextureNode}
 */
const texture3DLoad = ( ...params ) => texture3D( ...params ).setSampler( false );

/**
 * TSL function for creating a texture node that fetches/loads texels without interpolation.
 *
 * @tsl
 * @function
 * @param {?(Texture|TextureNode)} [value=EmptyTexture] - The texture.
 * @param {?Node<vec3>} [uvNode=null] - The uv node.
 * @param {?Node<int>} [levelNode=null] - The level node.
 * @returns {TextureNode}
 */
const texture3DLevel = ( value, uvNode, levelNode ) => texture3D( value, uvNode ).level( levelNode );

/**
 * A special type of reference node that allows to link values in
 * `userData` fields to node objects.
 * ```js
 * sprite.userData.rotation = 1; // stores individual rotation per sprite
 *
 * const material = new THREE.SpriteNodeMaterial();
 * material.rotationNode = userData( 'rotation', 'float' );
 * ```
 * Since `UserDataNode` is extended from {@link ReferenceNode}, the node value
 * will automatically be updated when the `rotation` user data field changes.
 *
 * @augments ReferenceNode
 */
class UserDataNode extends ReferenceNode {

	static get type() {

		return 'UserDataNode';

	}

	/**
	 * Constructs a new user data node.
	 *
	 * @param {string} property - The property name that should be referenced by the node.
	 * @param {string} inputType - The node data type of the reference.
	 * @param {?Object} [userData=null] - A reference to the `userData` object. If not provided, the `userData` property of the 3D object that uses the node material is evaluated.
	 */
	constructor( property, inputType, userData = null ) {

		super( property, inputType, userData );

		/**
		 * A reference to the `userData` object. If not provided, the `userData`
		 * property of the 3D object that uses the node material is evaluated.
		 *
		 * @type {?Object}
		 * @default null
		 */
		this.userData = userData;

	}

	/**
	 * Overwritten to make sure {@link ReferenceNode#reference} points to the correct
	 * `userData` field.
	 *
	 * @param {(NodeFrame|NodeBuilder)} state - The current state to evaluate.
	 * @return {Object} A reference to the `userData` field.
	 */
	updateReference( state ) {

		this.reference = this.userData !== null ? this.userData : state.object.userData;

		return this.reference;

	}

}

/**
 * TSL function for creating a user data node.
 *
 * @tsl
 * @function
 * @param {string} name - The property name that should be referenced by the node.
 * @param {string} inputType - The node data type of the reference.
 * @param {?Object} userData - A reference to the `userData` object. If not provided, the `userData` property of the 3D object that uses the node material is evaluated.
 * @returns {UserDataNode}
 */
const userData = ( name, inputType, userData ) => new UserDataNode( name, inputType, userData );

const _objectData = new WeakMap();

/**
 * A node for representing motion or velocity vectors. Foundation
 * for advanced post processing effects like motion blur or TRAA.
 *
 * The node keeps track of the model, view and projection matrices
 * of the previous frame and uses them to compute offsets in NDC space.
 * These offsets represent the final velocity.
 *
 * @augments Node
 */
class VelocityNode extends Node {

	static get type() {

		return 'VelocityNode';

	}

	/**
	 * Constructs a new vertex color node.
	 */
	constructor() {

		super( 'vec2' );

		/**
		 * An optional projection matrix that overrides the camera's projection matrix.
		 *
		 * @type {?Matrix4}
		 * @default null
		 */
		this.projectionMatrix = null;

		/**
		 * Overwritten since velocity nodes are updated per object.
		 *
		 * @type {string}
		 * @default 'object'
		 */
		this.updateType = NodeUpdateType.OBJECT;

		/**
		 * Overwritten since velocity nodes save data after the update.
		 *
		 * @type {string}
		 * @default 'object'
		 */
		this.updateAfterType = NodeUpdateType.OBJECT;

		/**
		 * Uniform node representing the previous model matrix in world space.
		 *
		 * @type {UniformNode<mat4>}
		 * @default null
		 */
		this.previousModelWorldMatrix = uniform( new Matrix4() );

		/**
		 * The projection matrix of the current frame.
		 *
		 * @type {UniformNode<mat4>}
		 */
		this.currentProjectionMatrix = uniform( new Matrix4() ).setGroup( renderGroup );

		/**
		 * Uniform node representing the previous projection matrix.
		 *
		 * @type {UniformNode<mat4>}
		 * @default null
		 */
		this.previousProjectionMatrix = uniform( new Matrix4() ).setGroup( renderGroup );

		/**
		 * Uniform node representing the previous view matrix.
		 *
		 * @type {UniformNode<mat4>}
		 * @default null
		 */
		this.previousCameraViewMatrix = uniform( new Matrix4() ).setGroup( renderGroup );

	}

	/**
	 * Sets the given projection matrix.
	 *
	 * @param {Matrix4} projectionMatrix - The projection matrix to set.
	 */
	setProjectionMatrix( projectionMatrix ) {

		this.projectionMatrix = projectionMatrix;

	}

	/**
	 * Updates velocity specific uniforms.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	update( { frameId, camera, object } ) {

		const previousModelMatrix = getPreviousMatrix( object );

		this.previousModelWorldMatrix.value.copy( previousModelMatrix );

		//

		const cameraData = getData( camera );

		if ( cameraData.frameId !== frameId ) {

			cameraData.frameId = frameId;

			if ( cameraData.previousProjectionMatrix === undefined ) {

				cameraData.previousProjectionMatrix = new Matrix4();
				cameraData.previousCameraViewMatrix = new Matrix4();

				cameraData.currentProjectionMatrix = new Matrix4();
				cameraData.currentCameraViewMatrix = new Matrix4();

				cameraData.previousProjectionMatrix.copy( this.projectionMatrix || camera.projectionMatrix );
				cameraData.previousCameraViewMatrix.copy( camera.matrixWorldInverse );

			} else {

				cameraData.previousProjectionMatrix.copy( cameraData.currentProjectionMatrix );
				cameraData.previousCameraViewMatrix.copy( cameraData.currentCameraViewMatrix );

			}

			cameraData.currentProjectionMatrix.copy( this.projectionMatrix || camera.projectionMatrix );
			cameraData.currentCameraViewMatrix.copy( camera.matrixWorldInverse );

			this.previousProjectionMatrix.value.copy( cameraData.previousProjectionMatrix );
			this.previousCameraViewMatrix.value.copy( cameraData.previousCameraViewMatrix );
			this.currentProjectionMatrix.value.copy( cameraData.currentProjectionMatrix );

		}

	}

	/**
	 * Overwritten to updated velocity specific uniforms.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	updateAfter( { object } ) {

		getPreviousMatrix( object ).copy( object.matrixWorld );

	}

	/**
	 * Implements the velocity computation based on the previous and current vertex data.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @return {Node<vec2>} The motion vector.
	 */
	setup( /*builder*/ ) {

		const previousModelViewMatrix = this.previousCameraViewMatrix.mul( this.previousModelWorldMatrix );

		const clipPositionCurrent = this.currentProjectionMatrix.mul( modelViewMatrix ).mul( positionLocal );
		const clipPositionPrevious = this.previousProjectionMatrix.mul( previousModelViewMatrix ).mul( positionPrevious );

		const ndcPositionCurrent = clipPositionCurrent.xy.div( clipPositionCurrent.w );
		const ndcPositionPrevious = clipPositionPrevious.xy.div( clipPositionPrevious.w );

		const velocity = sub( ndcPositionCurrent, ndcPositionPrevious );

		return velocity;

	}

}

function getData( object ) {

	let objectData = _objectData.get( object );

	if ( objectData === undefined ) {

		objectData = {};
		_objectData.set( object, objectData );

	}

	return objectData;

}

function getPreviousMatrix( object, index = 0 ) {

	const objectData = getData( object );

	let matrix = objectData[ index ];

	if ( matrix === undefined ) {

		objectData[ index ] = matrix = new Matrix4();
		objectData[ index ].copy( object.matrixWorld );

	}

	return matrix;

}

/**
 * TSL object that represents the velocity of a render pass.
 *
 * @tsl
 * @type {VelocityNode}
 */
const velocity = /*@__PURE__*/ nodeImmutable( VelocityNode );

/**
 * Represents a "Color Burn" blend mode.
 *
 * It's designed to darken the base layer's colors based on the color of the blend layer.
 * It significantly increases the contrast of the base layer, making the colors more vibrant and saturated.
 * The darker the color in the blend layer, the stronger the darkening and contrast effect on the base layer.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} base - The base color.
 * @param {Node<vec3>} blend - The blend color. A white (#ffffff) blend color does not alter the base color.
 * @return {Node<vec3>} The result.
 */
const blendBurn = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {

	return min( 1.0, base.oneMinus().div( blend ) ).oneMinus();

}, { base: 'vec3', blend: 'vec3', return: 'vec3' } );

/**
 * Represents a "Color Dodge" blend mode.
 *
 * It's designed to lighten the base layer's colors based on the color of the blend layer.
 * It significantly increases the brightness of the base layer, making the colors lighter and more vibrant.
 * The brighter the color in the blend layer, the stronger the lightening and contrast effect on the base layer.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} base - The base color.
 * @param {Node<vec3>} blend - The blend color. A black (#000000) blend color does not alter the base color.
 * @return {Node<vec3>} The result.
 */
const blendDodge = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {

	return min( base.div( blend.oneMinus() ), 1.0 );

}, { base: 'vec3', blend: 'vec3', return: 'vec3' } );

/**
 * Represents a "Screen" blend mode.
 *
 * Similar to `blendDodge()`, this mode also lightens the base layer's colors based on the color of the blend layer.
 * The "Screen" blend mode is better for general brightening whereas the "Dodge" results in more subtle and nuanced
 * effects.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} base - The base color.
 * @param {Node<vec3>} blend - The blend color. A black (#000000) blend color does not alter the base color.
 * @return {Node<vec3>} The result.
 */
const blendScreen = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {

	return base.oneMinus().mul( blend.oneMinus() ).oneMinus();

}, { base: 'vec3', blend: 'vec3', return: 'vec3' } );

/**
 * Represents a "Overlay" blend mode.
 *
 * It's designed to increase the contrast of the base layer based on the color of the blend layer.
 * It amplifies the existing colors and contrast in the base layer, making lighter areas lighter and darker areas darker.
 * The color of the blend layer significantly influences the resulting contrast and color shift in the base layer.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} base - The base color.
 * @param {Node<vec3>} blend - The blend color
 * @return {Node<vec3>} The result.
 */
const blendOverlay = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {

	return mix( base.mul( 2.0 ).mul( blend ), base.oneMinus().mul( 2.0 ).mul( blend.oneMinus() ).oneMinus(), step( 0.5, base ) );

}, { base: 'vec3', blend: 'vec3', return: 'vec3' } );

/**
 * This function blends two color based on their alpha values by replicating the behavior of `THREE.NormalBlending`.
 * It assumes both input colors have non-premultiplied alpha.
 *
 * @tsl
 * @function
 * @param {Node<vec4>} base - The base color.
 * @param {Node<vec4>} blend - The blend color
 * @return {Node<vec4>} The result.
 */
const blendColor = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {

	const outAlpha = blend.a.add( base.a.mul( blend.a.oneMinus() ) );

	return vec4( blend.rgb.mul( blend.a ).add( base.rgb.mul( base.a ).mul( blend.a.oneMinus() ) ).div( outAlpha ), outAlpha );

}, { base: 'vec4', blend: 'vec4', return: 'vec4' } );

/**
 * TSL object that represents the luminance coefficients of the current working color space.
 * It is evaluated once per build, so it can also be used as the default value of a parameter
 * of a function with layout.
 *
 * @tsl
 * @type {VarNode<vec3>}
 */
const workingLuminanceCoefficients = /*@__PURE__*/ Fn( () => vec3( ColorManagement.getLuminanceCoefficients( new Vector3() ) ) ).once()().toVar( 'workingLuminanceCoefficients' );

/**
 * Computes a grayscale value for the given RGB color value.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The color value to compute the grayscale for.
 * @param {Node<vec3>} [luminanceCoefficients] - The luminance coefficients. By default predefined values of the current working color space are used.
 * @return {Node<float>} The grayscale value.
 */
const grayscale = /*@__PURE__*/ Fn( ( [ color, luminanceCoefficients = workingLuminanceCoefficients ] ) => {

	return luminance( color.rgb, luminanceCoefficients );

}, { color: 'vec3', luminanceCoefficients: 'vec3', return: 'float' } );

/**
 * Super-saturates or desaturates the given RGB color.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The input color.
 * @param {Node<float>} [adjustment=1] - Specifies the amount of the conversion. A value under `1` desaturates the color, a value over `1` super-saturates it.
 * @param {Node<vec3>} [luminanceCoefficients] - The luminance coefficients. By default predefined values of the current working color space are used.
 * @return {Node<vec3>} The saturated color.
 */
const saturation = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ), luminanceCoefficients = workingLuminanceCoefficients ] ) => {

	return adjustment.mix( luminance( color.rgb, luminanceCoefficients ), color.rgb ).max( 0.0 );

}, { color: 'vec3', adjustment: 'float', luminanceCoefficients: 'vec3', return: 'vec3' } );

/**
 * Selectively enhance the intensity of less saturated RGB colors. Can result
 * in a more natural and visually appealing image with enhanced color depth
 * compared to {@link ColorAdjustment#saturation}.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The input color.
 * @param {Node<float>} [adjustment=0] - Controls the intensity of the vibrance effect.
 * @return {Node<vec3>} The updated color.
 */
const vibrance = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 0 ) ] ) => {

	const average = add( color.r, color.g, color.b ).div( 3.0 );

	const mx = color.r.max( color.g.max( color.b ) );
	const amt = mx.sub( average ).mul( adjustment ).mul( -3 );

	return mix( color.rgb, mx, amt ).max( 0.0 );

}, { color: 'vec3', adjustment: 'float', return: 'vec3' } );

/**
 * Updates the hue component of the given RGB color while preserving its luminance and saturation.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The input color.
 * @param {Node<float>} [adjustment=1] - Defines the degree of hue rotation in radians. A positive value rotates the hue clockwise, while a negative value rotates it counterclockwise.
 * @return {Node<vec3>} The updated color.
 */
const hue = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => {

	const k = vec3( 0.57735, 0.57735, 0.57735 );

	const cosAngle = adjustment.cos();

	return vec3( color.rgb.mul( cosAngle ).add( k.cross( color.rgb ).mul( adjustment.sin() ).add( k.mul( dot( k, color.rgb ).mul( cosAngle.oneMinus() ) ) ) ) ).max( 0.0 );

}, { color: 'vec3', adjustment: 'float', return: 'vec3' } );

/**
 * Computes the luminance for the given RGB color value.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The color value to compute the luminance for.
 * @param {?Node<vec3>} luminanceCoefficients - The luminance coefficients. By default predefined values of the current working color space are used.
 * @return {Node<float>} The luminance.
 */
const luminance = (
	color,
	luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3() ) )
) => dot( color, luminanceCoefficients );

/**
 * Color Decision List (CDL) v1.2
 *
 * Compact representation of color grading information, defined by slope, offset, power, and
 * saturation. The CDL should be typically be given input in a log space (such as LogC, ACEScc,
 * or AgX Log), and will return output in the same space. Output may require clamping >=0.
 *
 * @tsl
 * @function
 * @param {Node<vec4>} color Input (-Infinity < input < +Infinity)
 * @param {Node<vec3>} slope Slope (0 ≤ slope < +Infinity)
 * @param {Node<vec3>} offset Offset (-Infinity < offset < +Infinity; typically -1 < offset < 1)
 * @param {Node<vec3>} power Power (0 < power < +Infinity)
 * @param {Node<float>} saturation Saturation (0 ≤ saturation < +Infinity; typically 0 ≤ saturation < 4)
 * @param {Node<vec3>} luminanceCoefficients Luminance coefficients for saturation term, typically Rec. 709
 * @return {Node<vec4>} Output, -Infinity < output < +Infinity
 *
 * References:
 * - ASC CDL v1.2
 * - {@link https://blender.stackexchange.com/a/55239/43930}
 * - {@link https://docs.acescentral.com/specifications/acescc/}
 */
const cdl = /*@__PURE__*/ Fn( ( [
	color,
	slope = vec3( 1 ),
	offset = vec3( 0 ),
	power = vec3( 1 ),
	saturation = float( 1 ),
	// ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients.
	luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ) )
] ) => {

	// NOTE: The ASC CDL v1.2 defines a [0, 1] clamp on the slope+offset term, and another on the
	// saturation term. Per the ACEScc specification and Filament, limits may be omitted to support
	// if negative inputs to the power expression are avoided. We use `max( in, 0.0 )`
	// on final output, but the lower limit may not be required in all cases.

	const luma = color.rgb.dot( vec3( luminanceCoefficients ) );

	const v = max( color.rgb.mul( slope ).add( offset ), 0.0 );
	const pv = v.pow( power );

	If( v.r.greaterThan( 0.0 ), () => { v.r.assign( pv.r ); } ); // eslint-disable-line
	If( v.g.greaterThan( 0.0 ), () => { v.g.assign( pv.g ); } ); // eslint-disable-line
	If( v.b.greaterThan( 0.0 ), () => { v.b.assign( pv.b ); } ); // eslint-disable-line

	v.assign( luma.add( v.sub( luma ).mul( saturation ) ).max( 0 ) );

	return vec4( v.rgb, color.a );

}, { color: 'vec4', slope: 'vec3', offset: 'vec3', power: 'vec3', saturation: 'float', luminanceCoefficients: 'vec3', return: 'vec4' } );

/**
 * TSL function for creating a posterize effect which reduces the number of colors
 * in an image, resulting in a more blocky and stylized appearance.
 *
 * @tsl
 * @function
 * @param {Node} sourceNode - The input color.
 * @param {Node} stepsNode - Controls the intensity of the posterization effect. A lower number results in a more blocky appearance.
 * @returns {Node} The posterized color.
 */
const posterize = /*@__PURE__*/ Fn( ( [ source, steps ] ) => {

	return source.mul( steps ).floor().div( steps );

} );

let _sharedFramebuffer = null;

/**
 * `ViewportTextureNode` creates an internal texture for each node instance. This module
 * shares a texture across all instances of `ViewportSharedTextureNode`. It should
 * be the first choice when using data of the default/screen framebuffer for performance reasons.
 *
 * @augments ViewportTextureNode
 */
class ViewportSharedTextureNode extends ViewportTextureNode {

	static get type() {

		return 'ViewportSharedTextureNode';

	}

	/**
	 * Constructs a new viewport shared texture node.
	 *
	 * @param {Node} [uvNode=screenUV] - The uv node.
	 * @param {?Node} [levelNode=null] - The level node.
	 */
	constructor( uvNode = screenUV, levelNode = null ) {

		if ( _sharedFramebuffer === null ) {

			_sharedFramebuffer = new FramebufferTexture();

		}

		super( uvNode, levelNode, _sharedFramebuffer );

	}

	/**
	 * Overwritten so the method always returns the unique shared
	 * framebuffer texture.
	 *
	 * @return {FramebufferTexture} The shared framebuffer texture.
	 */
	getTextureForReference() {

		return _sharedFramebuffer;

	}

	updateReference() {

		return this;

	}

}

/**
 * TSL function for creating a shared viewport texture node.
 *
 * @tsl
 * @function
 * @param {?Node} [uvNode=screenUV] - The uv node.
 * @param {?Node} [levelNode=null] - The level node.
 * @returns {ViewportSharedTextureNode}
 */
const viewportSharedTexture = /*@__PURE__*/ nodeProxy( ViewportSharedTextureNode ).setParameterLength( 0, 2 );

const _size = /*@__PURE__*/ new Vector2();

/**
 * Represents the texture of a pass node.
 *
 * @augments TextureNode
 */
class PassTextureNode extends TextureNode {

	static get type() {

		return 'PassTextureNode';

	}

	/**
	 * Constructs a new pass texture node.
	 *
	 * @param {PassNode} passNode - The pass node.
	 * @param {Texture} texture - The output texture.
	 */
	constructor( passNode, texture ) {

		super( texture );

		/**
		 * A reference to the pass node.
		 *
		 * @type {PassNode}
		 */
		this.passNode = passNode;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @default true
		 * @readonly
		 */
		this.isPassTextureNode = true;

		this.setUpdateMatrix( false );

	}

	isCacheable( /*builder*/ ) {

		return true;

	}

	setup( builder ) {

		const properties = builder.getNodeProperties( this );
		properties.passNode = this.passNode;

		return super.setup( builder );

	}

	clone() {

		return new this.constructor( this.passNode, this.value );

	}

}

/**
 * An extension of `PassTextureNode` which allows to manage more than one
 * internal texture. Relevant for the `getPreviousTexture()` related API.
 *
 * @augments PassTextureNode
 */
class PassMultipleTextureNode extends PassTextureNode {

	static get type() {

		return 'PassMultipleTextureNode';

	}

	/**
	 * Constructs a new pass texture node.
	 *
	 * @param {PassNode} passNode - The pass node.
	 * @param {string} textureName - The output texture name.
	 * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not.
	 */
	constructor( passNode, textureName, previousTexture = false ) {

		// null is passed to the super call since this class does not
		// use an external texture for rendering pass data into. Instead
		// the texture is managed by the pass node itself

		super( passNode, null );

		/**
		 * The output texture name.
		 *
		 * @type {string}
		 */
		this.textureName = textureName;

		/**
		 * Whether previous frame data should be used or not.
		 *
		 * @type {boolean}
		 */
		this.previousTexture = previousTexture;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @default true
		 * @readonly
		 */
		this.isPassMultipleTextureNode = true;

	}

	/**
	 * Updates the texture reference of this node.
	 */
	updateTexture() {

		this.value = this.previousTexture ? this.passNode.getPreviousTexture( this.textureName ) : this.passNode.getTexture( this.textureName );

	}

	setup( builder ) {

		this.updateTexture();

		return super.setup( builder );

	}

	clone() {

		const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture );
		newNode.uvNode = this.uvNode;
		newNode.levelNode = this.levelNode;
		newNode.biasNode = this.biasNode;
		newNode.sampler = this.sampler;
		newNode.depthNode = this.depthNode;
		newNode.compareNode = this.compareNode;
		newNode.gradNode = this.gradNode;
		newNode.gatherNode = this.gatherNode;
		newNode.offsetNode = this.offsetNode;

		return newNode;

	}

}

/**
 * Represents a render pass (sometimes called beauty pass) in context of post processing.
 * This pass produces a render for the given scene and camera and can provide multiple outputs
 * via MRT for further processing.
 *
 * ```js
 * const postProcessing = new RenderPipeline( renderer );
 *
 * const scenePass = pass( scene, camera );
 *
 * postProcessing.outputNode = scenePass;
 * ```
 *
 * @augments Node
 */
class PassNode extends Node {

	static get type() {

		return 'PassNode';

	}

	/**
	 * Constructs a new pass node.
	 *
	 * @param {('color'|'depth')} scope - The scope of the pass. The scope determines whether the node outputs color or depth.
	 * @param {Scene} scene - A reference to the scene.
	 * @param {Camera} camera - A reference to the camera.
	 * @param {Object} options - Options for the internal render target.
	 */
	constructor( scope, scene, camera, options = {} ) {

		super( 'vec4' );

		/**
		 * The scope of the pass. The scope determines whether the node outputs color or depth.
		 *
		 * @type {('color'|'depth')}
		 */
		this.scope = scope;

		/**
		 * A reference to the scene.
		 *
		 * @type {Scene}
		 */
		this.scene = scene;

		/**
		 * A reference to the camera.
		 *
		 * @type {Camera}
		 */
		this.camera = camera;

		/**
		 * Options for the internal render target.
		 *
		 * @type {Object}
		 */
		this.options = options;

		/**
		 * The pass's pixel width. Will be kept automatically kept in sync with the renderer's width.
		 * @private
		 * @type {number}
		 * @default 1
		 */
		this._width = 1;

		/**
		 * The pass's pixel height. Will be kept automatically kept in sync with the renderer's height.
		 * @private
		 * @type {number}
		 * @default 1
		 */
		this._height = 1;

		const renderTarget = new RenderTarget( this._width, this._height, { type: HalfFloatType, ...options, } );
		renderTarget.texture.name = 'output';

		let depthTexture = null;

		if ( this.scope === PassNode.DEPTH || options.depthBuffer !== false ) {

			if ( options.depthTexture !== undefined ) {

				depthTexture = options.depthTexture;

			} else {

				depthTexture = new DepthTexture();

				if ( renderTarget.stencilBuffer === true ) {

					depthTexture.format = DepthStencilFormat;
					depthTexture.type = UnsignedInt248Type;

				}

			}

			depthTexture.isRenderTargetTexture = true;
			//depthTexture.type = FloatType;
			depthTexture.name = 'depth';

			renderTarget.depthTexture = depthTexture;

		}

		/**
		 * The pass's render target.
		 *
		 * @type {RenderTarget}
		 */
		this.renderTarget = renderTarget;

		/**
		 * An optional override material for the pass.
		 *
		 * @type {Material|null}
		 */
		this.overrideMaterial = null;

		/**
		 * Whether the pass is transparent.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.transparent = true;

		/**
		 * Whether the pass is opaque.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.opaque = true;

		this.lighting = null;

		/**
		 * Whether the renderer should automatically clear before rendering the pass.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.autoClear = options.autoClear !== undefined ? options.autoClear : true;

		/**
		 * Whether the color buffer should be cleared.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.autoClearColor = options.autoClearColor !== undefined ? options.autoClearColor : true;

		/**
		 * Whether the depth buffer should be cleared.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.autoClearDepth = options.autoClearDepth !== undefined ? options.autoClearDepth : true;

		/**
		 * Whether the stencil buffer should be cleared.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.autoClearStencil = options.autoClearStencil !== undefined ? options.autoClearStencil : true;

		/**
		 * An optional global context for the pass.
		 *
		 * @type {ContextNode|null}
		 */
		this.contextNode = null;

		/**
		 * A cache for the context node.
		 *
		 * @private
		 * @type {?Object}
		 * @default null
		 */
		this._contextNodeCache = null;

		/**
		 * A dictionary holding the internal result textures.
		 *
		 * @private
		 * @type {{ output: Texture, depth: ?DepthTexture }}
		 */
		this._textures = {
			output: renderTarget.texture
		};

		if ( depthTexture !== null ) {

			this._textures.depth = depthTexture;

		}

		/**
		 * A dictionary holding the internal texture nodes.
		 *
		 * @private
		 * @type {Object<string, TextureNode>}
		 */
		this._textureNodes = {};

		/**
		 * A dictionary holding the internal depth nodes.
		 *
		 * @private
		 * @type {Object}
		 */
		this._linearDepthNodes = {};

		/**
		 * A dictionary holding the internal viewZ nodes.
		 *
		 * @private
		 * @type {Object}
		 */
		this._viewZNodes = {};

		/**
		 * A dictionary holding the texture data of the previous frame.
		 * Used for computing velocity/motion vectors.
		 *
		 * @private
		 * @type {Object<string, Texture>}
		 */
		this._previousTextures = {};

		/**
		 * A dictionary holding the texture nodes of the previous frame.
		 * Used for computing velocity/motion vectors.
		 *
		 * @private
		 * @type {Object<string, TextureNode>}
		 */
		this._previousTextureNodes = {};

		/**
		 * The `near` property of the camera as a uniform.
		 *
		 * @private
		 * @type {UniformNode}
		 */
		this._cameraNear = uniform( 0 );

		/**
		 * The `far` property of the camera as a uniform.
		 *
		 * @private
		 * @type {UniformNode}
		 */
		this._cameraFar = uniform( 0 );

		/**
		 * A MRT node configuring the MRT settings.
		 *
		 * @private
		 * @type {?MRTNode}
		 * @default null
		 */
		this._mrt = null;

		/**
		 * Layer object for configuring the camera that is used
		 * to produce the pass.
		 *
		 * @private
		 * @type {?Layers}
		 * @default null
		 */
		this._layers = null;

		/**
		 * Scales the resolution of the internal render target.
		 *
		 * @private
		 * @type {number}
		 * @default 1
		 */
		this._resolutionScale = 1;

		/**
		 * Custom viewport definition.
		 *
		 * @private
		 * @type {?Vector4}
		 * @default null
		 */
		this._viewport = null;

		/**
		 * Custom scissor definition.
		 *
		 * @private
		 * @type {?Vector4}
		 * @default null
		 */
		this._scissor = null;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isPassNode = true;

		/**
		 * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders the
		 * scene once per frame in its {@link PassNode#updateBefore} method.
		 *
		 * @type {string}
		 * @default 'frame'
		 */
		this.updateBeforeType = NodeUpdateType.FRAME;

		/**
		 * This flag is used for global cache.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Sets the resolution scale for the pass.
	 * The resolution scale is a factor that is multiplied with the renderer's width and height.
	 *
	 * @param {number} resolutionScale - The resolution scale to set. A value of `1` means full resolution.
	 * @return {PassNode} A reference to this pass.
	 */
	setResolutionScale( resolutionScale ) {

		this._resolutionScale = resolutionScale;

		return this;

	}

	/**
	 * Gets the current resolution scale of the pass.
	 *
	 * @return {number} The current resolution scale. A value of `1` means full resolution.
	 */
	getResolutionScale() {

		return this._resolutionScale;

	}

	/**
	 * Sets the resolution for the pass.
	 * The resolution is a factor that is multiplied with the renderer's width and height.
	 *
	 * @param {number} resolution - The resolution to set. A value of `1` means full resolution.
	 * @return {PassNode} A reference to this pass.
	 * @deprecated since r181. Use {@link PassNode#setResolutionScale `setResolutionScale()`} instead.
	 */
	setResolution( resolution ) { // @deprecated, r181

		warn( 'PassNode: .setResolution() is deprecated. Use .setResolutionScale() instead.' );

		return this.setResolutionScale( resolution );

	}

	/**
	 * Gets the current resolution of the pass.
	 *
	 * @return {number} The current resolution. A value of `1` means full resolution.
	 * @deprecated since r181. Use {@link PassNode#getResolutionScale `getResolutionScale()`} instead.
	 */
	getResolution() { // @deprecated, r181

		warn( 'PassNode: .getResolution() is deprecated. Use .getResolutionScale() instead.' );

		return this.getResolutionScale();

	}

	/**
	 * Sets the layer configuration that should be used when rendering the pass.
	 *
	 * @param {Layers} layers - The layers object to set.
	 * @return {PassNode} A reference to this pass.
	 */
	setLayers( layers ) {

		this._layers = layers;

		return this;

	}

	/**
	 * Gets the current layer configuration of the pass.
	 *
	 * @return {?Layers} .
	 */
	getLayers() {

		return this._layers;

	}

	/**
	 * Sets the given MRT node to setup MRT for this pass.
	 *
	 * @param {MRTNode} mrt - The MRT object.
	 * @return {PassNode} A reference to this pass.
	 */
	setMRT( mrt ) {

		this._mrt = mrt;

		return this;

	}

	/**
	 * Returns the current MRT node.
	 *
	 * @return {MRTNode} The current MRT node.
	 */
	getMRT() {

		return this._mrt;

	}

	/**
	 * Returns the texture for the given output name.
	 *
	 * @param {string} name - The output name to get the texture for.
	 * @return {Texture} The texture.
	 */
	getTexture( name ) {

		let texture = this._textures[ name ];

		if ( texture === undefined ) {

			if ( name === 'depth' ) {

				throw new Error( 'THREE.PassNode: Depth texture is not available for this pass.' );

			}

			const refTexture = this.renderTarget.texture;

			texture = refTexture.clone();
			texture.name = name;

			this._textures[ name ] = texture;

			this.renderTarget.textures.push( texture );

		}

		return texture;

	}

	/**
	 * Returns the texture holding the data of the previous frame for the given output name.
	 *
	 * @param {string} name - The output name to get the texture for.
	 * @return {Texture} The texture holding the data of the previous frame.
	 */
	getPreviousTexture( name ) {

		let texture = this._previousTextures[ name ];

		if ( texture === undefined ) {

			texture = this.getTexture( name ).clone();

			this._previousTextures[ name ] = texture;

		}

		return texture;

	}

	/**
	 * Switches current and previous textures for the given output name.
	 *
	 * @param {string} name - The output name.
	 */
	toggleTexture( name ) {

		const prevTexture = this._previousTextures[ name ];

		if ( prevTexture !== undefined ) {

			const texture = this._textures[ name ];

			const index = this.renderTarget.textures.indexOf( texture );
			this.renderTarget.textures[ index ] = prevTexture;

			this._textures[ name ] = prevTexture;
			this._previousTextures[ name ] = texture;

			this._textureNodes[ name ].updateTexture();
			this._previousTextureNodes[ name ].updateTexture();

		}

	}

	/**
	 * Returns the texture node for the given output name.
	 *
	 * @param {string} [name='output'] - The output name to get the texture node for.
	 * @return {TextureNode} The texture node.
	 */
	getTextureNode( name = 'output' ) {

		let textureNode = this._textureNodes[ name ];

		if ( textureNode === undefined ) {

			textureNode = new PassMultipleTextureNode( this, name );
			textureNode.updateTexture();
			this._textureNodes[ name ] = textureNode;

		}

		return textureNode;

	}

	/**
	 * Returns the previous texture node for the given output name.
	 *
	 * @param {string} [name='output'] - The output name to get the previous texture node for.
	 * @return {TextureNode} The previous texture node.
	 */
	getPreviousTextureNode( name = 'output' ) {

		let textureNode = this._previousTextureNodes[ name ];

		if ( textureNode === undefined ) {

			if ( this._textureNodes[ name ] === undefined ) this.getTextureNode( name );

			textureNode = new PassMultipleTextureNode( this, name, true );
			textureNode.updateTexture();
			this._previousTextureNodes[ name ] = textureNode;

		}

		return textureNode;

	}

	/**
	 * Returns a viewZ node of this pass.
	 *
	 * @param {string} [name='depth'] - The output name to get the viewZ node for. In most cases the default `'depth'` can be used however the parameter exists for custom depth outputs.
	 * @return {Node} The viewZ node.
	 */
	getViewZNode( name = 'depth' ) {

		let viewZNode = this._viewZNodes[ name ];

		if ( viewZNode === undefined ) {

			const cameraNear = this._cameraNear;
			const cameraFar = this._cameraFar;

			this._viewZNodes[ name ] = viewZNode = perspectiveDepthToViewZ( this.getTextureNode( name ), cameraNear, cameraFar );

		}

		return viewZNode;

	}

	/**
	 * Returns a linear depth node of this pass.
	 *
	 * @param {string} [name='depth'] - The output name to get the linear depth node for. In most cases the default `'depth'` can be used however the parameter exists for custom depth outputs.
	 * @return {Node} The linear depth node.
	 */
	getLinearDepthNode( name = 'depth' ) {

		let linearDepthNode = this._linearDepthNodes[ name ];

		if ( linearDepthNode === undefined ) {

			const cameraNear = this._cameraNear;
			const cameraFar = this._cameraFar;
			const viewZNode = this.getViewZNode( name );

			// TODO: just if ( builder.camera.isPerspectiveCamera )

			this._linearDepthNodes[ name ] = linearDepthNode = viewZToOrthographicDepth( viewZNode, cameraNear, cameraFar );

		}

		return linearDepthNode;

	}

	/**
	 * Precompiles the pass.
	 *
	 * Note that this method must be called after the pass configuration is complete.
	 * So calls like `setMRT()` and `getTextureNode()` must proceed the precompilation.
	 *
	 * @async
	 * @param {Renderer} renderer - The renderer.
	 * @return {Promise} A Promise that resolves when the compile has been finished.
	 * @see {@link Renderer#compileAsync}
	 */
	async compileAsync( renderer ) {

		const currentRenderTarget = renderer.getRenderTarget();
		const currentMRT = renderer.getMRT();

		const currentContextNode = renderer.contextNode;

		// the render target is configured like in setup() and updateBefore(), so the pipelines match the ones of the render

		this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples;

		renderer.getDrawingBufferSize( _size );
		this.setSize( _size.width, _size.height );

		renderer.setRenderTarget( this.renderTarget );
		renderer.setMRT( this._mrt );

		if ( this.contextNode !== null ) renderer.contextNode = this._getContextNode( renderer );

		await renderer.compileAsync( this.scene, this.camera );

		renderer.setRenderTarget( currentRenderTarget );
		renderer.setMRT( currentMRT );
		renderer.contextNode = currentContextNode;

	}

	setup( { renderer } ) {

		this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples;

		this.renderTarget.texture.type = renderer.getOutputBufferType();

		if ( renderer.reversedDepthBuffer === true && this.renderTarget.depthTexture !== null ) {

			this.renderTarget.depthTexture.type = FloatType;

		}

		return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode();

	}

	updateBefore( frame ) {

		const { renderer } = frame;
		const { scene } = this;

		let camera;

		const outputRenderTarget = renderer.getOutputRenderTarget();

		if ( outputRenderTarget && outputRenderTarget.isXRRenderTarget === true ) {

			camera = renderer.xr.getCamera();

			renderer.xr.updateCamera( camera );

			_size.set( outputRenderTarget.width, outputRenderTarget.height );

		} else {

			camera = this.camera;

			renderer.getDrawingBufferSize( _size );

		}

		this.setSize( _size.width, _size.height );

		const currentRenderTarget = renderer.getRenderTarget();
		const currentMRT = renderer.getMRT();
		const currentAutoClear = renderer.autoClear;
		const currentAutoClearColor = renderer.autoClearColor;
		const currentAutoClearDepth = renderer.autoClearDepth;
		const currentAutoClearStencil = renderer.autoClearStencil;
		const currentTransparent = renderer.transparent;
		const currentOpaque = renderer.opaque;
		const currentLighting = renderer.lighting;
		const currentMask = camera.layers.mask;
		const currentContextNode = renderer.contextNode;
		const currentOverrideMaterial = scene.overrideMaterial;

		this._cameraNear.value = camera.near;
		this._cameraFar.value = camera.far;

		if ( this._layers !== null ) {

			camera.layers.mask = this._layers.mask;

		}

		for ( const name in this._previousTextures ) {

			this.toggleTexture( name );

		}

		if ( this.overrideMaterial !== null ) {

			scene.overrideMaterial = this.overrideMaterial;

		}

		renderer.setRenderTarget( this.renderTarget );
		renderer.setMRT( this._mrt );
		renderer.autoClear = this.autoClear;
		renderer.autoClearColor = this.autoClearColor;
		renderer.autoClearDepth = this.autoClearDepth;
		renderer.autoClearStencil = this.autoClearStencil;
		renderer.transparent = this.transparent;
		renderer.opaque = this.opaque;

		if ( this.lighting !== null ) {

			renderer.lighting = this.lighting;

		}

		if ( this.contextNode !== null ) {

			renderer.contextNode = this._getContextNode( renderer );

		}

		const currentSceneName = scene.name;

		scene.name = this.name ? this.name : scene.name;

		renderer.render( scene, camera );

		scene.name = currentSceneName;
		scene.overrideMaterial = currentOverrideMaterial;

		renderer.setRenderTarget( currentRenderTarget );
		renderer.setMRT( currentMRT );
		renderer.autoClear = currentAutoClear;
		renderer.autoClearColor = currentAutoClearColor;
		renderer.autoClearDepth = currentAutoClearDepth;
		renderer.autoClearStencil = currentAutoClearStencil;
		renderer.transparent = currentTransparent;
		renderer.opaque = currentOpaque;
		renderer.contextNode = currentContextNode;
		renderer.lighting = currentLighting;

		camera.layers.mask = currentMask;

	}

	/**
	 * Returns the context node of the pass merged with the context node of the renderer.
	 *
	 * @private
	 * @param {Renderer} renderer - The renderer.
	 * @return {ContextNode} The context node.
	 */
	_getContextNode( renderer ) {

		const rendererContextNode = renderer.contextNode;
		const cache = this._contextNodeCache;

		if ( cache === null || cache.version !== this.version || cache.rendererContextNode !== rendererContextNode || cache.rendererVersion !== rendererContextNode.version ) {

			this._contextNodeCache = {
				version: this.version,
				rendererContextNode,
				rendererVersion: rendererContextNode.version,
				context: context( mergeContext( rendererContextNode.getFlowContextData(), this.contextNode.getFlowContextData() ) )
			};

		}

		return this._contextNodeCache.context;

	}

	/**
	 * Sets the size of the pass's render target. Honors the pixel ratio.
	 *
	 * @param {number} width - The width to set.
	 * @param {number} height - The height to set.
	 */
	setSize( width, height ) {

		this._width = width;
		this._height = height;

		const effectiveWidth = Math.floor( this._width * this._resolutionScale );
		const effectiveHeight = Math.floor( this._height * this._resolutionScale );

		this.renderTarget.setSize( effectiveWidth, effectiveHeight );

		// scissor

		if ( this._scissor !== null ) {

			this.renderTarget.scissor.copy( this._scissor ).multiplyScalar( this._resolutionScale ).floor();
			this.renderTarget.scissorTest = true;

		} else {

			this.renderTarget.scissorTest = false;

		}

		// viewport

		if ( this._viewport !== null ) {

			this.renderTarget.viewport.copy( this._viewport ).multiplyScalar( this._resolutionScale ).floor();

		}

	}

	/**
	 * This method allows to define the pass's scissor rectangle. By default, the scissor rectangle is kept
	 * in sync with the pass's dimensions. To reverse the process and use auto-sizing again, call the method
	 * with `null` as the single argument.
	 *
	 * @param {?(number | Vector4)} x - The horizontal coordinate for the lower left corner of the box in logical pixel unit.
	 * Instead of passing four arguments, the method also works with a single four-dimensional vector.
	 * @param {number} y - The vertical coordinate for the lower left corner of the box in logical pixel unit.
	 * @param {number} width - The width of the scissor box in logical pixel unit.
	 * @param {number} height - The height of the scissor box in logical pixel unit.
	 */
	setScissor( x, y, width, height ) {

		if ( x === null ) {

			this._scissor = null;

		} else {

			if ( this._scissor === null ) this._scissor = new Vector4();

			if ( x.isVector4 ) {

				this._scissor.copy( x );

			} else {

				this._scissor.set( x, y, width, height );

			}

		}

	}

	/**
	 * This method allows to define the pass's viewport. By default, the viewport is kept in sync
	 * with the pass's dimensions. To reverse the process and use auto-sizing again, call the method
	 * with `null` as the single argument.
	 *
	 * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit.
	 * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin  in logical pixel unit.
	 * @param {number} width - The width of the viewport in logical pixel unit.
	 * @param {number} height - The height of the viewport in logical pixel unit.
	 */
	setViewport( x, y, width, height ) {

		if ( x === null ) {

			this._viewport = null;

		} else {

			if ( this._viewport === null ) this._viewport = new Vector4();

			if ( x.isVector4 ) {

				this._viewport.copy( x );

			} else {

				this._viewport.set( x, y, width, height );

			}

		}

	}

	/**
	 * Frees internal resources. Should be called when the node is no longer in use.
	 */
	dispose() {

		super.dispose();

		this.renderTarget.dispose();

	}


	/**
	 * @static
	 * @type {'color'}
	 * @default 'color'
	 */
	static get COLOR() {

		return 'color';

	}

	/**
	 * @static
	 * @type {'depth'}
	 * @default 'depth'
	 */
	static get DEPTH() {

		return 'depth';

	}

}

/**
 * TSL function for creating a pass node.
 *
 * @tsl
 * @function
 * @param {Scene} scene - A reference to the scene.
 * @param {Camera} camera - A reference to the camera.
 * @param {Object} options - Options for the internal render target.
 * @returns {PassNode}
 */
const pass = ( scene, camera, options ) => new PassNode( PassNode.COLOR, scene, camera, options );

/**
 * TSL function for creating a pass texture node.
 *
 * @tsl
 * @function
 * @param {PassNode} pass - The pass node.
 * @param {Texture} texture - The output texture.
 * @returns {PassTextureNode}
 */
const passTexture = ( pass, texture ) => new PassTextureNode( pass, texture );

/**
 * TSL function for creating a depth pass node.
 *
 * @tsl
 * @function
 * @param {Scene} scene - A reference to the scene.
 * @param {Camera} camera - A reference to the camera.
 * @param {Object} options - Options for the internal render target.
 * @returns {PassNode}
 */
const depthPass = ( scene, camera, options ) => new PassNode( PassNode.DEPTH, scene, camera, options );

/**
 * Represents a render pass for producing a toon outline effect on compatible objects.
 * Only 3D objects with materials of type `MeshToonMaterial` and `MeshToonNodeMaterial`
 * will receive the outline.
 *
 * ```js
 * const postProcessing = new RenderPipeline( renderer );
 *
 * const scenePass = toonOutlinePass( scene, camera );
 *
 * postProcessing.outputNode = scenePass;
 * ```
 * @augments PassNode
 */
class ToonOutlinePassNode extends PassNode {

	static get type() {

		return 'ToonOutlinePassNode';

	}

	/**
	 * Constructs a new outline pass node.
	 *
	 * @param {Scene} scene - A reference to the scene.
	 * @param {Camera} camera - A reference to the camera.
	 * @param {Node} colorNode - Defines the outline's color.
	 * @param {Node} thicknessNode - Defines the outline's thickness.
	 * @param {Node} alphaNode - Defines the outline's alpha.
	 */
	constructor( scene, camera, colorNode, thicknessNode, alphaNode ) {

		super( PassNode.COLOR, scene, camera );

		/**
		 * Defines the outline's color.
		 *
		 * @type {Node}
		 */
		this.colorNode = colorNode;

		/**
		 * Defines the outline's thickness.
		 *
		 * @type {Node}
		 */
		this.thicknessNode = thicknessNode;

		/**
		 * Defines the outline's alpha.
		 *
		 * @type {Node}
		 */
		this.alphaNode = alphaNode;

		/**
		 * The outline material for toon materials without node modifications.
		 *
		 * @private
		 * @type {NodeMaterial}
		 */
		this._outlineMaterial = this._createMaterial();

		/**
		 * An internal material cache.
		 *
		 * @private
		 * @type {Map<Material, NodeMaterial>}
		 */
		this._materialCache = new Map();

		/**
		 * Disposes the outline material of a disposed toon material.
		 *
		 * @private
		 * @type {Function}
		 */
		this._onMaterialDispose = ( event ) => {

			const originalMaterial = event.target;

			originalMaterial.removeEventListener( 'dispose', this._onMaterialDispose );

			this._materialCache.get( originalMaterial ).dispose();
			this._materialCache.delete( originalMaterial );

		};

		/**
		 * The name of this pass.
		 *
		 * @type {string}
		 * @default 'Outline Pass'
		 */
		this.name = 'Outline Pass';

	}

	updateBefore( frame ) {

		const { renderer } = frame;

		const currentRenderObjectFunction = renderer.getRenderObjectFunction();

		renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => {

			// only render outline for supported materials

			if ( material.isMeshToonMaterial || material.isMeshToonNodeMaterial ) {

				if ( material.wireframe === false ) {

					const outlineMaterial = this._getOutlineMaterial( material );
					renderer.renderObject( object, scene, camera, geometry, outlineMaterial, group, lightsNode, clippingContext );

				}

			}

			// default

			renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext );

		} );

		super.updateBefore( frame );

		renderer.setRenderObjectFunction( currentRenderObjectFunction );

	}

	/**
	 * Frees internal resources. Should be called when the node is no longer in use.
	 */
	dispose() {

		super.dispose();

		this._outlineMaterial.dispose();

		for ( const [ originalMaterial, outlineMaterial ] of this._materialCache ) {

			originalMaterial.removeEventListener( 'dispose', this._onMaterialDispose );

			outlineMaterial.dispose();

		}

		this._materialCache.clear();

	}

	/**
	 * Creates the material used for outline rendering.
	 *
	 * @private
	 * @return {NodeMaterial} The outline material.
	 */
	_createMaterial() {

		const material = new NodeMaterial();
		material.isMeshToonOutlineMaterial = true;
		material.name = 'Toon_Outline';
		material.side = BackSide;

		// vertex node

		const outlineNormal = normalLocal.negate();
		const mvp = cameraProjectionMatrix.mul( modelViewMatrix );

		const ratio = float( 1.0 ); // TODO: support outline thickness ratio for each vertex
		const pos = mvp.mul( vec4( positionLocal, 1.0 ) );
		const pos2 = mvp.mul( vec4( positionLocal.add( outlineNormal ), 1.0 ) );
		const norm = normalize( pos.sub( pos2 ) ); // NOTE: subtract pos2 from pos because BackSide objectNormal is negative

		material.vertexNode = pos.add( norm.mul( this.thicknessNode ).mul( pos.w ).mul( ratio ) );

		// color node

		material.colorNode = vec4( this.colorNode, this.alphaNode );

		return material;

	}

	/**
	 * For the given toon material, this method returns a corresponding
	 * outline material.
	 *
	 * @private
	 * @param {(MeshToonMaterial|MeshToonNodeMaterial)} originalMaterial - The toon material.
	 * @return {NodeMaterial} The outline material.
	 */
	_getOutlineMaterial( originalMaterial ) {

		if ( ! originalMaterial.positionNode ) return this._outlineMaterial; // early out if there are no node modifications

		let force = false;
		let outlineMaterial = this._materialCache.get( originalMaterial );

		// create

		if ( outlineMaterial === undefined ) {

			outlineMaterial = this._createMaterial();

			this._materialCache.set( originalMaterial, outlineMaterial );

			originalMaterial.addEventListener( 'dispose', this._onMaterialDispose );

			force = true;

		}

		// update

		if ( outlineMaterial.version !== originalMaterial.version || force === true ) {

			outlineMaterial.positionNode = originalMaterial.positionNode || null;
			outlineMaterial.version = originalMaterial.version;

		}

		return outlineMaterial;

	}

}

/**
 * TSL function for creating a toon outline pass node.
 *
 * @tsl
 * @function
 * @param {Scene} scene - A reference to the scene.
 * @param {Camera} camera - A reference to the camera.
 * @param {Color} color - Defines the outline's color.
 * @param {number} [thickness=0.003] - Defines the outline's thickness.
 * @param {number} [alpha=1] - Defines the outline's alpha.
 * @returns {ToonOutlinePassNode}
 */
const toonOutlinePass = ( scene, camera, color = new Color( 0, 0, 0 ), thickness = 0.003, alpha = 1 ) => new ToonOutlinePassNode( scene, camera, nodeObject( color ), nodeObject( thickness ), nodeObject( alpha ) );

/**
 * Linear tone mapping, exposure only.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The color that should be tone mapped.
 * @param {Node<float>} exposure - The exposure.
 * @return {Node<vec3>} The tone mapped color.
 */
const linearToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {

	return color.mul( exposure ).clamp();

}, { color: 'vec3', exposure: 'float', return: 'vec3' } );

/**
 * Reinhard tone mapping.
 *
 * Reference: {@link https://www.cs.utah.edu/docs/techreports/2002/pdf/UUCS-02-001.pdf}
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The color that should be tone mapped.
 * @param {Node<float>} exposure - The exposure.
 * @return {Node<vec3>} The tone mapped color.
 */
const reinhardToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {

	color = color.mul( exposure );

	return color.div( color.add( 1.0 ) ).clamp();

}, { color: 'vec3', exposure: 'float', return: 'vec3' } );

/**
 * Cineon tone mapping.
 *
 * Reference: {@link http://filmicworlds.com/blog/filmic-tonemapping-operators/}
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The color that should be tone mapped.
 * @param {Node<float>} exposure - The exposure.
 * @return {Node<vec3>} The tone mapped color.
 */
const cineonToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {

	// filmic operator by Jim Hejl and Richard Burgess-Dawson
	color = color.mul( exposure );
	color = color.sub( 0.004 ).max( 0.0 );

	const a = color.mul( color.mul( 6.2 ).add( 0.5 ) );
	const b = color.mul( color.mul( 6.2 ).add( 1.7 ) ).add( 0.06 );

	return a.div( b ).pow( 2.2 );

}, { color: 'vec3', exposure: 'float', return: 'vec3' } );

// source: https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs

const RRTAndODTFit = /*@__PURE__*/ Fn( ( [ color ] ) => {

	const a = color.mul( color.add( 0.0245786 ) ).sub( 0.000090537 );
	const b = color.mul( color.mul( 0.983729 ).add( 0.4329510 ) ).add( 0.238081 );

	return a.div( b );

}, { color: 'vec3', return: 'vec3' } );

/**
 * ACESFilmic tone mapping.
 *
 * Reference: {@link https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs}
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The color that should be tone mapped.
 * @param {Node<float>} exposure - The exposure.
 * @return {Node<vec3>} The tone mapped color.
 */
const acesFilmicToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {

	// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
	const ACESInputMat = mat3(
		0.59719, 0.35458, 0.04823,
		0.07600, 0.90834, 0.01566,
		0.02840, 0.13383, 0.83777
	);

	// ODT_SAT => XYZ => D60_2_D65 => sRGB
	const ACESOutputMat = mat3(
		1.60475, -0.53108, -0.07367,
		-0.10208, 1.10813, -605e-5,
		-327e-5, -0.07276, 1.07602
	);

	color = color.mul( exposure ).div( 0.6 );

	color = ACESInputMat.mul( color );

	// Apply RRT and ODT
	color = RRTAndODTFit( color );

	color = ACESOutputMat.mul( color );

	// Clamp to [0, 1]
	return color.clamp();

}, { color: 'vec3', exposure: 'float', return: 'vec3' } );

const LINEAR_REC2020_TO_LINEAR_SRGB = /*@__PURE__*/ mat3( vec3( 1.6605, -0.1246, -0.0182 ), vec3( -0.5876, 1.1329, -0.1006 ), vec3( -0.0728, -83e-4, 1.1187 ) );
const LINEAR_SRGB_TO_LINEAR_REC2020 = /*@__PURE__*/ mat3( vec3( 0.6274, 0.0691, 0.0164 ), vec3( 0.3293, 0.9195, 0.0880 ), vec3( 0.0433, 0.0113, 0.8956 ) );

const agxDefaultContrastApprox = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => {

	const x = vec3( x_immutable ).toVar();
	const x2 = vec3( x.mul( x ) ).toVar();
	const x4 = vec3( x2.mul( x2 ) ).toVar();

	return float( 15.5 ).mul( x4.mul( x2 ) ).sub( mul( 40.14, x4.mul( x ) ) ).add( mul( 31.96, x4 ).sub( mul( 6.868, x2.mul( x ) ) ).add( mul( 0.4298, x2 ).add( mul( 0.1191, x ).sub( 0.00232 ) ) ) );

}, { x: 'vec3', return: 'vec3' } );

/**
 * AgX tone mapping.
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The color that should be tone mapped.
 * @param {Node<float>} exposure - The exposure.
 * @return {Node<vec3>} The tone mapped color.
 */
const agxToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {

	const colortone = vec3( color ).toVar();
	const AgXInsetMatrix = mat3( vec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ), vec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ), vec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 ) );
	const AgXOutsetMatrix = mat3( vec3( 1.1271005818144368, -0.1413297634984383, -0.14132976349843826 ), vec3( -0.11060664309660323, 1.157823702216272, -0.11060664309660294 ), vec3( -0.016493938717834573, -0.016493938717834257, 1.2519364065950405 ) );
	const AgxMinEv = float( -12.47393 );
	const AgxMaxEv = float( 4.026069 );
	colortone.mulAssign( exposure );
	colortone.assign( LINEAR_SRGB_TO_LINEAR_REC2020.mul( colortone ) );
	colortone.assign( AgXInsetMatrix.mul( colortone ) );
	colortone.assign( max( colortone, 1e-10 ) );
	colortone.assign( log2( colortone ) );
	colortone.assign( colortone.sub( AgxMinEv ).div( AgxMaxEv.sub( AgxMinEv ) ) );
	colortone.assign( clamp( colortone, 0.0, 1.0 ) );
	colortone.assign( agxDefaultContrastApprox( colortone ) );
	colortone.assign( AgXOutsetMatrix.mul( colortone ) );
	colortone.assign( pow( max( vec3( 0.0 ), colortone ), vec3( 2.2 ) ) );
	colortone.assign( LINEAR_REC2020_TO_LINEAR_SRGB.mul( colortone ) );
	colortone.assign( clamp( colortone, 0.0, 1.0 ) );

	return colortone;

}, { color: 'vec3', exposure: 'float', return: 'vec3' } );

/**
 * Neutral tone mapping.
 *
 * Reference: {@link https://modelviewer.dev/examples/tone-mapping}
 *
 * @tsl
 * @function
 * @param {Node<vec3>} color - The color that should be tone mapped.
 * @param {Node<float>} exposure - The exposure.
 * @return {Node<vec3>} The tone mapped color.
 */
const neutralToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {

	const StartCompression = float( 0.8 - 0.04 );
	const Desaturation = float( 0.15 );

	color = color.mul( exposure );

	const x = min( color.r, min( color.g, color.b ) );
	const offset = select( x.lessThan( 0.08 ), x.sub( mul( 6.25, x.mul( x ) ) ), 0.04 );

	color.subAssign( offset );

	const peak = max( color.r, max( color.g, color.b ) );

	If( peak.lessThan( StartCompression ), () => {

		return color;

	} );

	const d = sub( 1, StartCompression );
	const newPeak = sub( 1, d.mul( d ).div( peak.add( d.sub( StartCompression ) ) ) );
	color.mulAssign( newPeak.div( peak ) );
	const g = sub( 1, div( 1, Desaturation.mul( peak.sub( newPeak ) ).add( 1 ) ) );

	return mix( color, vec3( newPeak ), g );

}, { color: 'vec3', exposure: 'float', return: 'vec3' } );

/**
 * This class represents native code sections. It is the base
 * class for modules like {@link FunctionNode} which allows to implement
 * functions with native shader languages.
 *
 * @augments Node
 */
class CodeNode extends Node {

	static get type() {

		return 'CodeNode';

	}

	/**
	 * Constructs a new code node.
	 *
	 * @param {string} [code=''] - The native code.
	 * @param {Array<Node>} [includes=[]] - An array of includes.
	 * @param {('js'|'wgsl'|'glsl')} [language=''] - The used language.
	 */
	constructor( code = '', includes = [], language = '' ) {

		super( 'code' );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isCodeNode = true;

		/**
		 * This flag is used for global cache.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

		/**
		 * The native code.
		 *
		 * @type {string}
		 * @default ''
		 */
		this.code = code;

		/**
		 * An array of includes
		 *
		 * @type {Array<Node>}
		 * @default []
		 */
		this.includes = includes;

		/**
		 * The used language.
		 *
		 * @type {('js'|'wgsl'|'glsl')}
		 * @default ''
		 */
		this.language = language;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Sets the includes of this code node.
	 *
	 * @param {Array<Node>} includes - The includes to set.
	 * @return {CodeNode} A reference to this node.
	 */
	setIncludes( includes ) {

		this.includes = includes;

		return this;

	}

	/**
	 * Returns the includes of this code node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Array<Node>} The includes.
	 */
	getIncludes( /*builder*/ ) {

		return this.includes;

	}

	generate( builder ) {

		const includes = this.getIncludes( builder );

		for ( const include of includes ) {

			include.build( builder, 'void' );

		}

		const nodeCode = builder.getCodeFromNode( this, this.getNodeType( builder ) );
		nodeCode.code = this.code;

		return nodeCode.code;

	}

	serialize( data ) {

		super.serialize( data );

		data.code = this.code;
		data.language = this.language;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.code = data.code;
		this.language = data.language;

	}

}

/**
 * TSL function for creating a code node.
 *
 * @tsl
 * @function
 * @param {string} [code] - The native code.
 * @param {?Array<Node>} [includes=[]] - An array of includes.
 * @param {?('js'|'wgsl'|'glsl')} [language=''] - The used language.
 * @returns {CodeNode}
 */
const code = /*@__PURE__*/ nodeProxy( CodeNode ).setParameterLength( 1, 3 );

/**
 * TSL function for creating a JS code node.
 *
 * @tsl
 * @function
 * @param {string} src - The native code.
 * @param {Array<Node>} includes - An array of includes.
 * @returns {CodeNode}
 */
const js = ( src, includes ) => code( src, includes, 'js' );

/**
 * TSL function for creating a WGSL code node.
 *
 * @tsl
 * @function
 * @param {string} src - The native code.
 * @param {Array<Node>} includes - An array of includes.
 * @returns {CodeNode}
 */
const wgsl = ( src, includes ) => code( src, includes, 'wgsl' );

/**
 * TSL function for creating a GLSL code node.
 *
 * @tsl
 * @function
 * @param {string} src - The native code.
 * @param {Array<Node>} includes - An array of includes.
 * @returns {CodeNode}
 */
const glsl = ( src, includes ) => code( src, includes, 'glsl' );

/**
 * This class represents a native shader function. It can be used to implement
 * certain aspects of a node material with native shader code. There are two predefined
 * TSL functions for easier usage.
 *
 * - `wgslFn`: Creates a WGSL function node.
 * - `glslFn`: Creates a GLSL function node.
 *
 * A basic example with one include looks like so:
 *
 * ```js
 * const desaturateWGSLFn = wgslFn( `
 *	fn desaturate( color:vec3<f32> ) -> vec3<f32> {
 *		let lum = vec3<f32>( 0.299, 0.587, 0.114 );
 *		return vec3<f32>( dot( lum, color ) );
 *	}`
 *);
 * const someWGSLFn = wgslFn( `
 *	fn someFn( color:vec3<f32> ) -> vec3<f32> {
 * 		return desaturate( color );
 * 	}
 * `, [ desaturateWGSLFn ] );
 * material.colorNode = someWGSLFn( { color: texture( map ) } );
 *```
 * @augments CodeNode
 */
class FunctionNode extends CodeNode {

	static get type() {

		return 'FunctionNode';

	}

	/**
	 * Constructs a new function node.
	 *
	 * @param {string} [code=''] - The native code.
	 * @param {Array<Node>} [includes=[]] - An array of includes.
	 * @param {('js'|'wgsl'|'glsl')} [language=''] - The used language.
	 */
	constructor( code = '', includes = [], language = '' ) {

		super( code, includes, language );

	}

	/**
	 * Returns the type of this function node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The type.
	 */
	generateNodeType( builder ) {

		return this.getNodeFunction( builder ).type;

	}

	/**
	 * Returns the type of a member of this function node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} name - The name of the member.
	 * @return {string} The type of the member.
	 */
	getMemberType( builder, name ) {

		const type = this.getNodeType( builder );

		const structType = builder.getStructTypeNode( type );

		return structType.getMemberType( builder, name );

	}

	/**
	 * Returns the inputs of this function node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {Array<NodeFunctionInput>} The inputs.
	 */
	getInputs( builder ) {

		return this.getNodeFunction( builder ).inputs;

	}

	/**
	 * Returns the node function for this function node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {NodeFunction} The node function.
	 */
	getNodeFunction( builder ) {

		const nodeData = builder.getDataFromNode( this );

		let nodeFunction = nodeData.nodeFunction;

		if ( nodeFunction === undefined ) {

			nodeFunction = builder.parser.parseFunction( this.code );

			nodeData.nodeFunction = nodeFunction;

		}

		return nodeFunction;

	}

	generate( builder, output ) {

		const nodeFunction = this.getNodeFunction( builder );
		const nodeData = builder.getDataFromNode( this );

		const type = nodeFunction.type;

		let propertyName = nodeData.functionPropertyName;

		if ( propertyName === undefined ) {

			// The includes and the function code are generated once per builder and shader stage,
			// further calls only need the function name.

			super.generate( builder );

			const name = nodeFunction.name;
			const nodeCode = builder.getCodeFromNode( this, type );

			if ( name !== '' ) {

				// use a custom property name

				nodeCode.name = name;

				builder.registerDeclaration( nodeCode );

			}

			propertyName = builder.getPropertyName( nodeCode );

			nodeCode.code = nodeFunction.getCode( propertyName ) + '\n';

			nodeData.functionPropertyName = propertyName;

		}

		if ( output === 'property' ) {

			return propertyName;

		} else {

			return builder.format( `${ propertyName }()`, type, output );

		}

	}

}

const nativeFn = ( code, includes = [], language = '' ) => {

	const functionNode = new FunctionNode( code, includes, language );

	const fn = ( ...params ) => functionNode.call( ...params );

	return nodeProxyConstructor( fn, functionNode );

};

const glslFn = ( code, includes ) => nativeFn( code, includes, 'glsl' );
const wgslFn = ( code, includes ) => nativeFn( code, includes, 'wgsl' );

/**
 * Returns a node that represents the `z` coordinate in view space
 * for the current fragment. It's a different representation of the
 * default depth value.
 *
 * This value can be part of a computation that defines how the fog
 * density increases when moving away from the camera.
 *
 * @param {NodeBuilder} builder - The current node builder.
 * @return {Node} The viewZ node.
 */
function getViewZNode( builder ) {

	let viewZ;

	const getViewZ = builder.context.getViewZ;

	if ( getViewZ !== undefined ) {

		viewZ = getViewZ( this );

	}

	return ( viewZ || positionView.z ).negate();

}

/**
 * Constructs a new range factor node.
 *
 * @tsl
 * @function
 * @param {Node} near - Defines the near value.
 * @param {Node} far - Defines the far value.
 */
const rangeFogFactor = /*@__PURE__*/ Fn( ( [ near, far ], builder ) => {

	const viewZ = getViewZNode( builder );

	return smoothstep( near, far, viewZ );

} );

/**
 * Represents an exponential squared fog. This type of fog gives
 * a clear view near the camera and a faster than exponentially
 * densening fog farther from the camera.
 *
 * @tsl
 * @function
 * @param {Node} density - Defines the fog density.
 */
const densityFogFactor = /*@__PURE__*/ Fn( ( [ density ], builder ) => {

	const viewZ = getViewZNode( builder );

	return density.mul( density, viewZ, viewZ ).negate().exp().oneMinus();

} );

/**
 * Constructs a new height fog factor node. This fog factor requires a Y-up coordinate system.
 *
 * @tsl
 * @function
 * @param {Node} density - Defines the fog density.
 * @param {Node} height - The height threshold in world space. Everything below this y-coordinate is affected by fog.
 */
const exponentialHeightFogFactor = /*@__PURE__*/ Fn( ( [ density, height ], builder ) => {

	const viewZ = getViewZNode( builder );

	const distance = height.sub( positionWorld.y ).max( 0 ).toConst();
	const m = distance.mul( viewZ ).toConst();

	return density.mul( density, m, m ).negate().exp().oneMinus();

} );

/**
 * This class can be used to configure a fog for the scene.
 * Nodes of this type are assigned to `Scene.fogNode`.
 *
 * @tsl
 * @function
 * @param {Node} color - Defines the color of the fog.
 * @param {Node} factor - Defines how the fog is factored in the scene.
 */
const fog = /*@__PURE__*/ Fn( ( [ color, factor ] ) => {

	return vec4( factor.toFloat().mix( output.rgb, color.toVec3() ), output.a );

} );

let _rangeId = 0;

// Per-object seed shared by all range nodes, so the values differ between objects.

const objectSeed = /*@__PURE__*/ uniform( 0, 'uint' ).onObjectUpdate( ( { object } ) => {

	const objectId = object !== null ? object.id : 0; // compute has no object

	return Math.imul( objectId, 0x9E3779B9 ) >>> 0;

} );

/**
 * `RangeNode` generates random per-instance values in a defined range.
 * An exemplary use case for this utility node is to generate random per-instance
 * colors:
 * ```js
 * const material = new MeshBasicNodeMaterial();
 * material.colorNode = range( new Color( 0x000000 ), new Color( 0xFFFFFF ) );
 * const mesh = new InstancedMesh( geometry, material, count );
 * ```
 * @augments Node
 */
class RangeNode extends Node {

	static get type() {

		return 'RangeNode';

	}

	/**
	 * Constructs a new range node.
	 *
	 * @param {Node<any>} [minNode=float()] - A node defining the lower bound of the range.
	 * @param {Node<any>} [maxNode=float()] - A node defining the upper bound of the range.
	 */
	constructor( minNode = float(), maxNode = float() ) {

		super();

		/**
		 *  A node defining the lower bound of the range.
		 *
		 * @type {Node<any>}
		 * @default float()
		 */
		this.minNode = minNode;

		/**
		 *  A node defining the upper bound of the range.
		 *
		 * @type {Node<any>}
		 * @default float()
		 */
		this.maxNode = maxNode;

		/**
		 * Salt of the random sequence. It uses a counter of range nodes instead of the
		 * global node id, so the values only change when range nodes are added or removed.
		 *
		 * @private
		 * @type {number}
		 */
		this._salt = Math.imul( _rangeId ++, 0x85EBCA6B ) >>> 0;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Returns the vector length which is computed based on the range definition.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {number} The vector length.
	 */
	getVectorLength( builder ) {

		const minNode = this.getConstNode( this.minNode );
		const maxNode = this.getConstNode( this.maxNode );

		const minLength = builder.getTypeLength( getValueType( minNode.value ) );
		const maxLength = builder.getTypeLength( getValueType( maxNode.value ) );

		return minLength > maxLength ? minLength : maxLength;

	}

	/**
	 * This method is overwritten since the node type is inferred from range definition.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		return builder.getTypeFromLength( this.getVectorLength( builder ) );

	}

	/**
	 * Returns a constant node from the given node by traversing it.
	 *
	 * @param {Node} node - The node to traverse.
	 * @returns {Node} The constant node, if found.
	 */
	getConstNode( node ) {

		let output = null;

		node.traverse( n => {

			if ( n.isConstNode === true ) {

				output = n;

			}

		} );

		if ( output === null ) {

			throw new NodeError( 'THREE.TSL: No "ConstNode" found in node graph.', this.stackTrace );

		}

		return output;

	}

	setup( builder ) {

		const minNode = this.getConstNode( this.minNode );
		const maxNode = this.getConstNode( this.maxNode );

		const minValue = minNode.value;
		const maxValue = maxNode.value;

		const minLength = builder.getTypeLength( getValueType( minValue ) );
		const maxLength = builder.getTypeLength( getValueType( maxValue ) );

		const min = new Vector4();
		const max = new Vector4();

		if ( minLength === 1 ) min.setScalar( minValue );
		else if ( minValue.isColor ) min.set( minValue.r, minValue.g, minValue.b, 1 );
		else min.set( minValue.x, minValue.y, minValue.z || 0, minValue.w || 0 );

		if ( maxLength === 1 ) max.setScalar( maxValue );
		else if ( maxValue.isColor ) max.set( maxValue.r, maxValue.g, maxValue.b, 1 );
		else max.set( maxValue.x, maxValue.y, maxValue.z || 0, maxValue.w || 0 );

		// The values are hashed from the instance index instead of stored per object, so they work
		// for any instance count. The salt decorrelates range nodes.

		const seed = objectSeed.bitXor( uint( this._salt ) );

		const index = instanceIndex.mul( 4 ).add( seed );
		const random = vec4( hash( index ), hash( index.add( 1 ) ), hash( index.add( 2 ) ), hash( index.add( 3 ) ) );

		return mix( vec4( min ), vec4( max ), random ).convert( this.getNodeType( builder ) );

	}

}

/**
 * TSL function for creating a range node.
 *
 * @tsl
 * @function
 * @param {Node<any>} [minNode=float()] - A node defining the lower bound of the range.
 * @param {Node<any>} [maxNode=float()] - A node defining the upper bound of the range.
 * @returns {RangeNode}
 */
const range = /*@__PURE__*/ nodeProxy( RangeNode ).setParameterLength( 2 );

/**
 * `ComputeBuiltinNode` represents a compute-scope builtin value that expose information
 * about the currently running dispatch and/or the device it is running on.
 *
 * This node can only be used with a WebGPU backend.
 *
 * @augments Node
 */
class ComputeBuiltinNode extends Node {

	static get type() {

		return 'ComputeBuiltinNode';

	}

	/**
	 * Constructs a new compute builtin node.
	 *
	 * @param {string} builtinName - The built-in name.
	 * @param {string} nodeType - The node type.
	 */
	constructor( builtinName, nodeType ) {

		super( nodeType );

		/**
		 * The built-in name.
		 *
		 * @private
		 * @type {string}
		 */
		this._builtinName = builtinName;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * This method is overwritten since hash is derived from the built-in name.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The hash.
	 */
	getHash( builder ) {

		return this.getBuiltinName( builder );

	}

	/**
	 * This method is overwritten since the node type is simply derived from `nodeType`..
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( /*builder*/ ) {

		return this.nodeType;

	}

	/**
	 * Sets the builtin name.
	 *
	 * @param {string} builtinName - The built-in name.
	 * @return {ComputeBuiltinNode} A reference to this node.
	 */
	setBuiltinName( builtinName ) {

		this._builtinName = builtinName;

		return this;

	}

	/**
	 * Returns the builtin name.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The builtin name.
	 */
	getBuiltinName( /*builder*/ ) {

		return this._builtinName;

	}

	/**
	 * Whether the current node builder has the builtin or not.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {boolean} Whether the builder has the builtin or not.
	 */
	hasBuiltin( builder ) {

		return builder.hasBuiltin( this._builtinName );

	}

	generate( builder, output ) {

		const builtinName = this.getBuiltinName( builder );
		const nodeType = this.getNodeType( builder );

		if ( builder.shaderStage === 'compute' ) {

			return builder.format( builtinName, nodeType, output );

		} else {

			warn( `TSL: Compute built-in value "${builtinName}" can not be accessed in the ${builder.shaderStage} stage` );
			return builder.generateConst( nodeType );

		}

	}

	serialize( data ) {

		super.serialize( data );

		data.global = this.global;
		data._builtinName = this._builtinName;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.global = data.global;
		this._builtinName = data._builtinName;

	}

}

/**
 * TSL function for creating a compute builtin node.
 *
 * @tsl
 * @function
 * @param {string} name - The built-in name.
 * @param {string} nodeType - The node type.
 * @returns {ComputeBuiltinNode}
 */
const computeBuiltin = ( name, nodeType ) => new ComputeBuiltinNode( name, nodeType );

/**
 * Represents the number of workgroups dispatched by the compute shader.
 * ```js
 * // Run 512 invocations/threads with a workgroup size of 128.
 * const computeFn = Fn(() => {
 *
 *     // numWorkgroups.x = 4
 *     storageBuffer.element(0).assign(numWorkgroups.x)
 *
 * })().compute(512, [128]);
 *
 * // Run 512 invocations/threads with the default workgroup size of 64.
 * const computeFn = Fn(() => {
 *
 *     // numWorkgroups.x = 8
 *     storageBuffer.element(0).assign(numWorkgroups.x)
 *
 * })().compute(512);
 * ```
 *
 * @tsl
 * @type {ComputeBuiltinNode<uvec3>}
 */
const numWorkgroups = /*@__PURE__*/ computeBuiltin( 'numWorkgroups', 'uvec3' );

/**
 * Represents the 3-dimensional index of the workgroup the current compute invocation belongs to.
 * ```js
 * // Execute 12 compute threads with a workgroup size of 3.
 * const computeFn = Fn( () => {
 *
 * 	If( workgroupId.x.mod( 2 ).equal( 0 ), () => {
 *
 * 		storageBuffer.element( instanceIndex ).assign( instanceIndex );
 *
 * 	} ).Else( () => {
 *
 * 		storageBuffer.element( instanceIndex ).assign( 0 );
 *
 * 	} );
 *
 * } )().compute( 12, [ 3 ] );
 *
 * // workgroupId.x =  [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3];
 * // Buffer Output =  [0, 1, 2, 0, 0, 0, 6, 7, 8, 0, 0, 0];
 * ```
 *
 * @tsl
 * @type {ComputeBuiltinNode<uvec3>}
 */
const workgroupId = /*@__PURE__*/ computeBuiltin( 'workgroupId', 'uvec3' );

/**
 * A non-linearized 3-dimensional representation of the current invocation's position within a 3D global grid.
 *
 * @tsl
 * @type {ComputeBuiltinNode<uvec3>}
 */
const globalId = /*@__PURE__*/ computeBuiltin( 'globalId', 'uvec3' );
/**
 * A non-linearized 3-dimensional representation of the current invocation's position within a 3D workgroup grid.
 *
 * @tsl
 * @type {ComputeBuiltinNode<uvec3>}
 */
const localId = /*@__PURE__*/ computeBuiltin( 'localId', 'uvec3' );

/**
 * A device dependent variable that exposes the size of the current invocation's subgroup.
 *
 * @tsl
 * @type {ComputeBuiltinNode<uint>}
 */
const subgroupSize = /*@__PURE__*/ computeBuiltin( 'subgroupSize', 'uint' );

/**
 * Represents a GPU control barrier that synchronizes compute operations within a given scope.
 *
 * This node can only be used with a WebGPU backend.
 *
 * @augments Node
 */
class BarrierNode extends Node {

	/**
	 * Constructs a new barrier node.
	 *
	 * @param {string} scope - The scope defines the behavior of the node.
	 */
	constructor( scope ) {

		super();

		this.scope = scope;

		this.isBarrierNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	setup( builder ) {

		builder.allowEarlyReturns = false;
		builder.allowGlobalVariables = false;

	}

	generate( builder ) {

		const { scope } = this;
		const { renderer, shaderStage } = builder;

		const barrierMethod = `${scope}Barrier`;

		if ( shaderStage !== 'compute' ) {

			error( `TSL: "${barrierMethod}" is not supported in the ${shaderStage} stage and can only be executed in compute.` );

		}

		if ( renderer.backend.isWebGLBackend === true ) {

			builder.addFlowCode( `\t// ${barrierMethod}() \n` );

		} else {

			builder.addLineFlowCode( `${barrierMethod}()`, this );

		}

	}

}

/**
 * TSL function for creating a barrier node.
 *
 * @tsl
 * @function
 * @param {string} scope - The scope defines the behavior of the node..
 * @returns {BarrierNode}
 */
const barrier = /*@__PURE__*/ nodeProxy( BarrierNode );

/**
 * TSL function for creating a workgroup barrier. All compute shader
 * invocations must wait for each invocation within a workgroup to
 * complete before the barrier can be surpassed.
 *
 * @tsl
 * @function
 * @returns {BarrierNode}
 */
const workgroupBarrier = () => barrier( 'workgroup' ).toStack();

/**
 * TSL function for creating a storage barrier. All invocations must
 * wait for each access to variables within the 'storage' address space
 * to complete before the barrier can be passed.
 *
 * @tsl
 * @function
 * @returns {BarrierNode}
 */
const storageBarrier = () => barrier( 'storage' ).toStack();

/**
 * TSL function for creating a texture barrier. All invocations must
 * wait for each access to variables within the 'texture' address space
 * to complete before the barrier can be passed.
 *
 * @tsl
 * @function
 * @returns {BarrierNode}
 */
const textureBarrier = () => barrier( 'texture' ).toStack();

/**
 * Represents an element of a 'workgroup' scoped buffer.
 *
 * @augments ArrayElementNode
 */
class WorkgroupInfoElementNode extends ArrayElementNode {

	/**
	 * Constructs a new workgroup info element node.
	 *
	 * @param {Node} workgroupInfoNode - The workgroup info node.
	 * @param {Node} indexNode - The index node that defines the element access.
	 */
	constructor( workgroupInfoNode, indexNode ) {

		super( workgroupInfoNode, indexNode );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isWorkgroupInfoElementNode = true;

	}

	generate( builder, output ) {

		let snippet;

		const isAssignContext = builder.isContextAssign();
		snippet = super.generate( builder );

		if ( isAssignContext !== true ) {

			const type = this.getNodeType( builder );

			snippet = builder.format( snippet, type, output );

		}

		// TODO: Possibly activate clip distance index on index access rather than from clipping context

		return snippet;

	}

}

/**
 * A node allowing the user to create a 'workgroup' scoped buffer within the
 * context of a compute shader. Typically, workgroup scoped buffers are
 * created to hold data that is transferred from a global storage scope into
 * a local workgroup scope. For invocations within a workgroup, data
 * access speeds on 'workgroup' scoped buffers can be significantly faster
 * than similar access operations on globally accessible storage buffers.
 *
 * This node can only be used with a WebGPU backend.
 *
 * @augments Node
 */
class WorkgroupInfoNode extends Node {

	/**
	 * Constructs a new workgroup info node.
	 *
	 * @param {string} scope - The address space the buffer is scoped to. Currently only `'Workgroup'` is used.
	 * @param {string} bufferType - The data type of a 'workgroup' scoped buffer element.
	 * @param {number} [bufferCount=0] - The number of elements in the buffer.
	 */
	constructor( scope, bufferType, bufferCount = 0 ) {

		super( bufferType );

		/**
		 * The buffer type.
		 *
		 * @type {string}
		 */
		this.bufferType = bufferType;

		/**
		 * The buffer count.
		 *
		 * @type {number}
		 * @default 0
		 */
		this.bufferCount = bufferCount;

		/**
		 * Whether the node is atomic or not.
		 *
		 * @type {boolean}
		 * @default false
		 */
		this.isAtomic = false;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isWorkgroupInfoNode = true;

		/**
		 * The data type of the array buffer.
		 *
		 * @type {string}
		 */
		this.elementType = bufferType;

		/**
		 * The address space the buffer is scoped to. Currently only `'Workgroup'` is used.
		 *
		 * @type {string}
		 */
		this.scope = scope;

		/**
		 * The name of the workgroup scoped buffer.
		 *
		 * @type {string}
		 * @default ''
		 */
		this.name = '';

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Sets the name of this node.
	 *
	 * @param {string} name - The name to set.
	 * @return {WorkgroupInfoNode} A reference to this node.
	 */
	setName( name ) {

		this.name = name;

		return this;

	}

	/**
	 * Sets the name/label of this node.
	 *
	 * @deprecated
	 * @param {string} name - The name to set.
	 * @return {WorkgroupInfoNode} A reference to this node.
	 */
	label( name ) {

		warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.', new StackTrace() ); // @deprecated r179

		return this.setName( name );

	}

	/**
	 * Sets the scope of this node.
	 *
	 * @param {string} scope - The scope to set.
	 * @return {WorkgroupInfoNode} A reference to this node.
	 */
	setScope( scope ) {

		this.scope = scope;

		return this;

	}

	/**
	 * Defines whether the node is atomic or not.
	 *
	 * @param {boolean} value - The atomic flag.
	 * @return {WorkgroupInfoNode} A reference to this node.
	 */
	setAtomic( value ) {

		this.isAtomic = value;

		return this;

	}

	/**
	 * Convenience method for making this node atomic.
	 *
	 * @return {WorkgroupInfoNode} A reference to this node.
	 */
	toAtomic() {

		return this.setAtomic( true );

	}


	/**
	 * The data type of the array buffer.
	 *
	 * @return {string} The element type.
	 */
	getElementType() {

		return this.elementType;

	}

	/**
	 * Overwrites the default implementation since the input type
	 * is inferred from the scope.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( /*builder*/ ) {

		return `${this.scope}Array`;

	}

	/**
	 * This method can be used to access elements via an index node.
	 *
	 * @param {IndexNode} indexNode - indexNode.
	 * @return {WorkgroupInfoElementNode} A reference to an element.
	 */
	element( indexNode ) {

		return new WorkgroupInfoElementNode( this, indexNode );

	}

	generate( builder ) {

		if ( builder.shaderStage !== 'compute' ) {

			error( 'TSL: "workgroupArray()" can only be executed within the compute shader stage' );

		}

		const name = ( this.name !== '' ) ? this.name : `${this.scope}Array_${this.id}`;

		return builder.getScopedArray( name, this.scope.toLowerCase(), this.bufferType, this.bufferCount, this.isAtomic );

	}

}

/**
 * TSL function for creating a workgroup info node.
 * Creates a new 'workgroup' scoped array buffer.
 *
 * @tsl
 * @function
 * @param {string} type - The data type of a 'workgroup' scoped buffer element.
 * @param {number} [count=0] - The number of elements in the buffer.
 * @returns {WorkgroupInfoNode}
 */
const workgroupArray = ( type, count ) => new WorkgroupInfoNode( 'Workgroup', type, count );

/**
 * `AtomicFunctionNode` represents any function that can operate on atomic variable types
 * within a shader. In an atomic function, any modification to an atomic variable will
 * occur as an indivisible step with a defined order relative to other modifications.
 * Accordingly, even if multiple atomic functions are modifying an atomic variable at once
 * atomic operations will not interfere with each other.
 *
 * This node can only be used with a WebGPU backend.
 *
 * @augments Node
 */
class AtomicFunctionNode extends Node {

	static get type() {

		return 'AtomicFunctionNode';

	}

	/**
	 * Constructs a new atomic function node.
	 *
	 * @param {string} method - The signature of the atomic function to construct.
	 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
	 * @param {Node} valueNode - The value that mutates the atomic variable.
	 */
	constructor( method, pointerNode, valueNode ) {

		super( 'uint' );

		/**
		 * The signature of the atomic function to construct.
		 *
		 * @type {string}
		 */
		this.method = method;

		/**
		 * An atomic variable or element of an atomic buffer.
		 *
		 * @type {Node}
		 */
		this.pointerNode = pointerNode;

		/**
		 * A value that modifies the atomic variable.
		 *
		 * @type {Node}
		 */
		this.valueNode = valueNode;

		/**
		 * Creates a list of the parents for this node for detecting if the node needs to return a value.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.parents = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Overwrites the default implementation to return the type of
	 * the pointer node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The input type.
	 */
	getInputType( builder ) {

		return this.pointerNode.getNodeType( builder );

	}

	/**
	 * Overwritten since the node type is inferred from the input type.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @return {string} The node type.
	 */
	generateNodeType( builder ) {

		return this.getInputType( builder );

	}

	generate( builder ) {

		const method = this.method;

		if ( builder.shaderStage === 'vertex' ) {

			error( `TSL: "${this.method}" is not supported in the vertex stage.` );

		}

		const properties = builder.getNodeProperties( this );
		const parents = properties.parents;

		const type = this.getNodeType( builder );
		const inputType = this.getInputType( builder );

		const a = this.pointerNode;
		const b = this.valueNode;

		const params = [];

		params.push( `&${ a.build( builder, inputType ) }` );

		if ( b !== null ) {

			params.push( b.build( builder, inputType ) );


		}

		const methodSnippet = `${ builder.getMethod( method, type ) }( ${ params.join( ', ' ) } )`;
		const isVoid = parents ? ( parents.length === 1 && parents[ 0 ].isStackNode === true ) : false;

		if ( isVoid ) {

			builder.addLineFlowCode( methodSnippet, this );

		} else {

			// The result is stored in a constant, declared in the block where the operation is generated.
			const nodeVar = builder.getVarFromNode( this, null, type, undefined, true, true );
			const propertyName = builder.getPropertyName( nodeVar );

			builder.addLineFlowCode( `${ builder.generateLetStatement( type, propertyName ) } = ${ methodSnippet }`, this );

			return propertyName;

		}

	}

	static get ATOMIC_LOAD() {

		return 'atomicLoad';

	}

	static get ATOMIC_STORE() {

		return 'atomicStore';

	}

	static get ATOMIC_ADD() {

		return 'atomicAdd';

	}

	static get ATOMIC_SUB() {

		return 'atomicSub';

	}

	static get ATOMIC_MAX() {

		return 'atomicMax';

	}

	static get ATOMIC_MIN() {

		return 'atomicMin';

	}

	static get ATOMIC_AND() {

		return 'atomicAnd';

	}

	static get ATOMIC_OR() {

		return 'atomicOr';

	}

	static get ATOMIC_XOR() {

		return 'atomicXor';

	}

}

/**
 * TSL function for creating an atomic function node.
 *
 * @tsl
 * @function
 * @param {string} method - The signature of the atomic function to construct.
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicNode = /*@__PURE__*/ nodeProxy( AtomicFunctionNode );

/**
 * TSL function for appending an atomic function call into the programmatic flow of a compute shader.
 *
 * @tsl
 * @function
 * @param {string} method - The signature of the atomic function to construct.
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicFunc = ( method, pointerNode, valueNode ) => {

	return atomicNode( method, pointerNode, valueNode ).toStack();

};

/**
 * Loads the value stored in the atomic variable.
 *
 * @tsl
 * @function
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @returns {AtomicFunctionNode}
 */
const atomicLoad = ( pointerNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_LOAD, pointerNode, null );

/**
 * Stores a value in the atomic variable.
 *
 * @tsl
 * @function
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicStore = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_STORE, pointerNode, valueNode );

/**
 * Increments the value stored in the atomic variable.
 *
 * @tsl
 * @function
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicAdd = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_ADD, pointerNode, valueNode );

/**
 * Decrements the value stored in the atomic variable.
 *
 * @tsl
 * @function
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicSub = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_SUB, pointerNode, valueNode );

/**
 * Stores in an atomic variable the maximum between its current value and a parameter.
 *
 * @tsl
 * @function
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicMax = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_MAX, pointerNode, valueNode );

/**
 * Stores in an atomic variable the minimum between its current value and a parameter.
 *
 * @tsl
 * @function
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicMin = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_MIN, pointerNode, valueNode );

/**
 * Stores in an atomic variable the bitwise AND of its value with a parameter.
 *
 * @tsl
 * @function
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicAnd = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_AND, pointerNode, valueNode );

/**
 * Stores in an atomic variable the bitwise OR of its value with a parameter.
 *
 * @tsl
 * @function
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicOr = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_OR, pointerNode, valueNode );

/**
 * Stores in an atomic variable the bitwise XOR of its value with a parameter.
 *
 * @tsl
 * @function
 * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
 * @param {Node} valueNode - The value that mutates the atomic variable.
 * @returns {AtomicFunctionNode}
 */
const atomicXor = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_XOR, pointerNode, valueNode );

/**
 * This class represents a set of built in WGSL shader functions that sync
 * synchronously execute an operation across a subgroup, or 'warp', of compute
 * or fragment shader invocations within a workgroup. Typically, these functions
 * will synchronously execute an operation using data from all active invocations
 * within the subgroup, then broadcast that result to all active invocations. In
 * other graphics APIs, subgroup functions are also referred to as wave intrinsics
 * (DirectX/HLSL) or warp intrinsics (CUDA).
 *
 * @augments Node
 */
class SubgroupFunctionNode extends Node {

	static get type() {

		return 'SubgroupFunctionNode';

	}

	/**
	 * Constructs a new function node.
	 *
	 * @param {string} method - The subgroup/wave intrinsic method to construct.
	 * @param {Node} [aNode=null] - The method's first argument.
	 * @param {Node} [bNode=null] - The method's second argument.
	 */
	constructor( method, aNode = null, bNode = null ) {

		super();

		/**
		 * The subgroup/wave intrinsic method to construct.
		 *
		 * @type {string}
		 */
		this.method = method;

		/**
		 * The method's first argument.
		 *
		 * @type {Node}
		 */
		this.aNode = aNode;

		/**
		 * The method's second argument.
		 *
		 * @type {Node}
		 */
		this.bNode = bNode;

	}

	getInputType( builder ) {

		const aType = this.aNode ? this.aNode.getNodeType( builder ) : null;
		const bType = this.bNode ? this.bNode.getNodeType( builder ) : null;

		const aLen = builder.isMatrix( aType ) ? 0 : builder.getTypeLength( aType );
		const bLen = builder.isMatrix( bType ) ? 0 : builder.getTypeLength( bType );

		if ( aLen > bLen ) {

			return aType;

		} else {

			return bType;

		}

	}

	setup( builder ) {

		builder.enableSubGroups();

		return super.setup( builder );

	}

	generateNodeType( builder ) {

		const method = this.method;

		if ( method === SubgroupFunctionNode.SUBGROUP_ELECT ) {

			return 'bool';

		} else if ( method === SubgroupFunctionNode.SUBGROUP_BALLOT ) {

			return 'uvec4';

		} else {

			return this.getInputType( builder );

		}

	}

	generate( builder, output ) {

		const method = this.method;

		if ( builder.shaderStage === 'vertex' ) {

			error( `TSL: "${this.method}" is not supported in the vertex shader stage.` );

		}

		const type = this.getNodeType( builder );
		const inputType = this.getInputType( builder );

		const a = this.aNode;
		const b = this.bNode;

		const params = [];

		if (
			method === SubgroupFunctionNode.SUBGROUP_BROADCAST ||
			method === SubgroupFunctionNode.SUBGROUP_SHUFFLE ||
			method === SubgroupFunctionNode.QUAD_BROADCAST
		) {

			const bType = b.getNodeType( builder );

			params.push(
				a.build( builder, type ),
				b.build( builder, bType === 'float' ? 'int' : type )
			);

		} else if (
			method === SubgroupFunctionNode.SUBGROUP_SHUFFLE_XOR ||
			method === SubgroupFunctionNode.SUBGROUP_SHUFFLE_DOWN ||
			method === SubgroupFunctionNode.SUBGROUP_SHUFFLE_UP
		) {

			params.push(
				a.build( builder, type ),
				b.build( builder, 'uint' )
			);

		} else {

			if ( a !== null ) params.push( a.build( builder, inputType ) );
			if ( b !== null ) params.push( b.build( builder, inputType ) );

		}

		const paramsString = params.length === 0 ? '()' : `( ${params.join( ', ' )} )`;

		return builder.format( `${ builder.getMethod( method, type ) }${paramsString}`, type, output );



	}

	serialize( data ) {

		super.serialize( data );

		data.method = this.method;

	}

	deserialize( data ) {

		super.deserialize( data );

		this.method = data.method;

	}

	// 0 inputs
	static get SUBGROUP_ELECT() {

		return 'subgroupElect';

	}

	// 1 input
	static get SUBGROUP_BALLOT() {

		return 'subgroupBallot';

	}

	static get SUBGROUP_ADD() {

		return 'subgroupAdd';

	}

	static get SUBGROUP_INCLUSIVE_ADD() {

		return 'subgroupInclusiveAdd';

	}

	static get SUBGROUP_EXCLUSIVE_ADD() {

		return 'subgroupExclusiveAdd';

	}

	static get SUBGROUP_MUL() {

		return 'subgroupMul';

	}

	static get SUBGROUP_INCLUSIVE_MUL() {

		return 'subgroupInclusiveMul';

	}

	static get SUBGROUP_EXCLUSIVE_MUL() {

		return 'subgroupExclusiveMul';

	}

	static get SUBGROUP_AND() {

		return 'subgroupAnd';

	}

	static get SUBGROUP_OR() {

		return 'subgroupOr';

	}

	static get SUBGROUP_XOR() {

		return 'subgroupXor';

	}

	static get SUBGROUP_MIN() {

		return 'subgroupMin';

	}

	static get SUBGROUP_MAX() {

		return 'subgroupMax';

	}

	static get SUBGROUP_ALL() {

		return 'subgroupAll';

	}

	static get SUBGROUP_ANY() {

		return 'subgroupAny';

	}

	static get SUBGROUP_BROADCAST_FIRST() {

		return 'subgroupBroadcastFirst';

	}

	static get QUAD_SWAP_X() {

		return 'quadSwapX';

	}

	static get QUAD_SWAP_Y() {

		return 'quadSwapY';

	}

	static get QUAD_SWAP_DIAGONAL() {

		return 'quadSwapDiagonal';

	}

	// 2 inputs
	static get SUBGROUP_BROADCAST() {

		return 'subgroupBroadcast';

	}

	static get SUBGROUP_SHUFFLE() {

		return 'subgroupShuffle';

	}

	static get SUBGROUP_SHUFFLE_XOR() {

		return 'subgroupShuffleXor';

	}

	static get SUBGROUP_SHUFFLE_UP() {

		return 'subgroupShuffleUp';

	}

	static get SUBGROUP_SHUFFLE_DOWN() {

		return 'subgroupShuffleDown';

	}

	static get QUAD_BROADCAST() {

		return 'quadBroadcast';

	}

}

/**
 * Returns true if this invocation has the lowest subgroup_invocation_id
 * among active invocations in the subgroup.
 *
 * @tsl
 * @method
 * @return {bool} The result of the computation.
 */
const subgroupElect = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_ELECT ).setParameterLength( 0 );

/**
 * Returns a set of bitfields where the bit corresponding to subgroup_invocation_id
 * is 1 if pred is true for that active invocation and 0 otherwise.
 *
 * @tsl
 * @method
 * @param {bool} pred - A boolean that sets the bit corresponding to the invocations subgroup invocation id.
 * @return {vec4<u32>}- A bitfield corresponding to the pred value of each subgroup invocation.
 */
const subgroupBallot = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_BALLOT ).setParameterLength( 1 );

/**
 * A reduction that adds e among all active invocations and returns that result.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the reduction by the current invocation.
 * @return {number} The accumulated result of the reduction operation.
 */
const subgroupAdd = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_ADD ).setParameterLength( 1 );

/**
 * An inclusive scan returning the sum of e for all active invocations with subgroup_invocation_id less than or equal to this invocation.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the inclusive scan by the current invocation.
 * @return {number} The accumulated result of the inclusive scan operation.
 */
const subgroupInclusiveAdd = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_INCLUSIVE_ADD ).setParameterLength( 1 );

/**
 * An exclusive scan that returns the sum of e for all active invocations with subgroup_invocation_id less than this invocation.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the exclusive scan by the current invocation.
 * @return {number} The accumulated result of the exclusive scan operation.
 */
const subgroupExclusiveAdd = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_EXCLUSIVE_ADD ).setParameterLength( 1 );

/**
 * A reduction that multiplies e among all active invocations and returns that result.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the reduction by the current invocation.
 * @return {number} The accumulated result of the reduction operation.
 */
const subgroupMul = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_MUL ).setParameterLength( 1 );

/**
 * An inclusive scan returning the product of e for all active invocations with subgroup_invocation_id less than or equal to this invocation.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the inclusive scan by the current invocation.
 * @return {number} The accumulated result of the inclusive scan operation.
 */
const subgroupInclusiveMul = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_INCLUSIVE_MUL ).setParameterLength( 1 );

/**
 * An exclusive scan that returns the product of e for all active invocations with subgroup_invocation_id less than this invocation.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the exclusive scan by the current invocation.
 * @return {number} The accumulated result of the exclusive scan operation.
 */
const subgroupExclusiveMul = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_EXCLUSIVE_MUL ).setParameterLength( 1 );

/**
 * A reduction that performs a bitwise and of e among all active invocations and returns that result.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the reduction by the current invocation.
 * @return {number} The result of the reduction operation.
 */
const subgroupAnd = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_AND ).setParameterLength( 1 );

/**
 * A reduction that performs a bitwise or of e among all active invocations and returns that result.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the reduction by the current invocation.
 * @return {number} The result of the reduction operation.
 */
const subgroupOr = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_OR ).setParameterLength( 1 );

/**
 * A reduction that performs a bitwise xor of e among all active invocations and returns that result.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the reduction by the current invocation.
 * @return {number} The result of the reduction operation.
 */
const subgroupXor = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_XOR ).setParameterLength( 1 );

/**
 * A reduction that performs a min of e among all active invocations and returns that result.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the reduction by the current invocation.
 * @return {number} The result of the reduction operation.
 */
const subgroupMin = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_MIN ).setParameterLength( 1 );

/**
 * A reduction that performs a max of e among all active invocations and returns that result.
 *
 * @tsl
 * @method
 * @param {number} e - The value provided to the reduction by the current invocation.
 * @return {number} The result of the reduction operation.
 */
const subgroupMax = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_MAX ).setParameterLength( 1 );

/**
 * Returns true if e is true for all active invocations in the subgroup.
 *
 * @tsl
 * @method
 * @param {boolean} e - The predicate provided by the current invocation.
 * @return {bool} The result of the computation.
 */
const subgroupAll = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_ALL ).setParameterLength( 1 );

/**
 * Returns true if e is true for any active invocation in the subgroup
 *
 * @tsl
 * @method
 * @param {boolean} e - The predicate provided by the current invocation.
 * @return {bool} The result of the computation.
 */
const subgroupAny = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_ANY ).setParameterLength( 1 );

/**
 * Broadcasts e from the active invocation with the lowest subgroup_invocation_id in the subgroup to all other active invocations.
 *
 * @tsl
 * @method
 * @param {number} e - The value to broadcast from the lowest subgroup invocation.
 * @return {number} The broadcast value.
 */
const subgroupBroadcastFirst = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_BROADCAST_FIRST ).setParameterLength( 1 );

/**
 * Swaps e between invocations in the quad in the X direction.
 *
 * @tsl
 * @method
 * @param {number} e - The value to swap from the current invocation.
 * @return {number} The value received from the swap operation.
 */
const quadSwapX = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.QUAD_SWAP_X ).setParameterLength( 1 );

/**
 * Swaps e between invocations in the quad in the Y direction.
 *
 * @tsl
 * @method
 * @param {number} e - The value to swap from the current invocation.
 * @return {number} The value received from the swap operation.
 */
const quadSwapY = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.QUAD_SWAP_Y ).setParameterLength( 1 );

/**
 * Swaps e between invocations in the quad diagonally.
 *
 * @tsl
 * @method
 * @param {number} e - The value to swap from the current invocation.
 * @return {number} The value received from the swap operation.
 */
const quadSwapDiagonal = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.QUAD_SWAP_DIAGONAL ).setParameterLength( 1 );

/**
 * Broadcasts e from the invocation whose subgroup_invocation_id matches id, to all active invocations.
 *
 * @tsl
 * @method
 * @param {number} e - The value to broadcast from subgroup invocation 'id'.
 * @param {number} id - The subgroup invocation to broadcast from.
 * @return {number} The broadcast value.
 */
const subgroupBroadcast = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_BROADCAST ).setParameterLength( 2 );

/**
 * Returns v from the active invocation whose subgroup_invocation_id matches id
 *
 * @tsl
 * @method
 * @param {number} v - The value to return from subgroup invocation id^mask.
 * @param {number} id - The subgroup invocation which returns the value v.
 * @return {number} The broadcast value.
 */
const subgroupShuffle = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_SHUFFLE ).setParameterLength( 2 );

/**
 * Returns v from the active invocation whose subgroup_invocation_id matches subgroup_invocation_id ^ mask.
 *
 * @tsl
 * @method
 * @param {number} v - The value to return from subgroup invocation id^mask.
 * @param {number} mask - A bitmask that determines the target invocation via a XOR operation.
 * @return {number} The broadcast value.
 */
const subgroupShuffleXor = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_SHUFFLE_XOR ).setParameterLength( 2 );

/**
 * Returns v from the active invocation whose subgroup_invocation_id matches subgroup_invocation_id - delta
 *
 * @tsl
 * @method
 * @param {number} v - The value to return from subgroup invocation id^mask.
 * @param {number} delta - A value that offsets the current in.
 * @return {number} The broadcast value.
 */
const subgroupShuffleUp = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_SHUFFLE_UP ).setParameterLength( 2 );

/**
 * Returns v from the active invocation whose subgroup_invocation_id matches subgroup_invocation_id + delta
 *
 * @tsl
 * @method
 * @param {number} v - The value to return from subgroup invocation id^mask.
 * @param {number} delta - A value that offsets the current subgroup invocation.
 * @return {number} The broadcast value.
 */
const subgroupShuffleDown = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_SHUFFLE_DOWN ).setParameterLength( 2 );

/**
 * Broadcasts e from the quad invocation with id equal to id.
 *
 * @tsl
 * @method
 * @param {number} e - The value to broadcast.
 * @param {number} id - The quad invocation id to broadcast from.
 * @return {number} The broadcast value.
 */
const quadBroadcast = /*@__PURE__*/ nodeProxyAssignable( SubgroupFunctionNode, SubgroupFunctionNode.QUAD_BROADCAST ).setParameterLength( 2 );

let uniformsLib;

function getLightData( light ) {

	uniformsLib = uniformsLib || new WeakMap();

	let uniforms = uniformsLib.get( light );

	if ( uniforms === undefined ) uniformsLib.set( light, uniforms = {} );

	return uniforms;

}

/**
 * TSL function for getting a shadow matrix uniform node for the given light.
 *
 * @tsl
 * @function
 * @param {Light} light -The light source.
 * @returns {UniformNode<mat4>} The shadow matrix uniform node.
 */
function lightShadowMatrix( light ) {

	const data = getLightData( light );

	return data.shadowMatrix || ( data.shadowMatrix = uniform( 'mat4' ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => {

		// normally, shadow matrices are updated in ShadowNode. However, if the shadow matrix is used outside
		// of shadow rendering (like in ProjectorLightNode), the shadow matrix still requires an update

		if ( light.castShadow !== true || frame.renderer.shadowMap.enabled === false ) {

			if ( light.shadow.camera.coordinateSystem !== frame.camera.coordinateSystem ) {

				light.shadow.camera.coordinateSystem = frame.camera.coordinateSystem;
				light.shadow.camera.updateProjectionMatrix();

			}

			light.shadow.updateMatrices( light );

		}

		return light.shadow.matrix;

	} ) );

}

/**
 * TSL function for getting projected uv coordinates for the given light.
 * Relevant when using maps with spot lights.
 *
 * @tsl
 * @function
 * @param {Light} light -The light source.
 * @param {Node<vec3>} [position=positionWorld] -The position to project.
 * @returns {Node<vec3>} The projected uvs.
 */
function lightProjectionUV( light, position = positionWorld ) {

	const spotLightCoord = lightShadowMatrix( light ).mul( position );
	const projectionUV = spotLightCoord.xyz.div( spotLightCoord.w );

	return projectionUV;

}

/**
 * TSL function for getting the position in world space for the given light.
 *
 * @tsl
 * @function
 * @param {Light} light -The light source.
 * @returns {UniformNode<vec3>} The light's position in world space.
 */
function lightPosition( light ) {

	const data = getLightData( light );

	return data.position || ( data.position = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( _, self ) => self.value.setFromMatrixPosition( light.matrixWorld ) ) );

}

/**
 * TSL function for getting the light target position in world space for the given light.
 *
 * @tsl
 * @function
 * @param {Light} light -The light source.
 * @returns {UniformNode<vec3>} The light target position in world space.
 */
function lightTargetPosition( light ) {

	const data = getLightData( light );

	return data.targetPosition || ( data.targetPosition = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( _, self ) => self.value.setFromMatrixPosition( light.target.matrixWorld ) ) );

}

/**
 * TSL function for getting the position in view space for the given light.
 *
 * @tsl
 * @function
 * @param {Light} light - The light source.
 * @returns {Node<vec3>} The light's position in view space.
 */
function lightViewPosition( light ) {

	const data = getLightData( light );

	return data.viewPosition || ( data.viewPosition = ( Fn( ( { camera } ) => {

		// Each sub-camera of an array camera has its own view space.

		if ( camera.isArrayCamera && camera.cameras.length > 0 ) {

			return cameraViewMatrix.mul( vec4( lightPosition( light ), 1 ) ).xyz;

		}

		return data.viewPositionUniform || ( data.viewPositionUniform = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( { camera }, self ) => {

			self.value = self.value || new Vector3();
			self.value.setFromMatrixPosition( light.matrixWorld );

			self.value.applyMatrix4( camera.matrixWorldInverse );

		} ) );

	} ).once() )() );

}

/**
 * TSL function for getting the light target direction for the given light.
 *
 * @tsl
 * @function
 * @param {Light} light -The light source.
 * @returns {Node<vec3>} The light's target direction.
 */
const lightTargetDirection = ( light ) => cameraViewMatrix.transformDirection( lightPosition( light ).sub( lightTargetPosition( light ) ) );

/**
 * A node representing the total diffuse light.
 *
 * @type {Node<vec3>}
 */
const totalDiffuse = property( 'vec3', 'totalDiffuse' );

/**
 * A node representing the total specular light.
 *
 * @type {Node<vec3>}
 */
const totalSpecular = property( 'vec3', 'totalSpecular' );

/**
 * A node representing the outgoing light.
 *
 * @type {Node<vec3>}
 */
const outgoingLight = property( 'vec3', 'outgoingLight' );

/**
 * Sorts an array of lights in ascending order by their IDs.
 *
 * @private
 * @param {Array<Light>} lights - The array of lights to sort.
 * @return {Array<Light>} The sorted array of lights.
 */
const sortLights = ( lights ) => {

	return lights.sort( ( a, b ) => a.id - b.id );

};

/**
 * Finds and returns a lighting node associated with a specific light ID.
 *
 * @private
 * @param {number} id - The ID of the light to search for.
 * @param {Array<LightingNode>} lightNodes - The array of lighting nodes to search within.
 * @return {?LightingNode} The matching lighting node, or null if not found.
 */
const getLightNodeById = ( id, lightNodes ) => {

	for ( const lightNode of lightNodes ) {

		if ( lightNode.isAnalyticLightNode && lightNode.light.id === id ) {

			return lightNode;

		}

	}

	return null;

};

/**
 * WeakMap cache mapping light objects to their corresponding lighting node instances.
 *
 * @private
 * @type {WeakMap<Light, LightingNode>}
 */
const _lightsNodeRef = /*@__PURE__*/ new WeakMap();

/**
 * Array used to temporarily store light IDs and shadow casting states for hashing.
 *
 * @private
 * @type {Array<number>}
 */
const _hashData = [];

/**
 * This node represents the scene's lighting and manages the lighting model's life cycle
 * for the current build 3D object. It is responsible for computing the total outgoing
 * light in a given lighting context.
 *
 * @augments Node
 */
class LightsNode extends Node {

	static get type() {

		return 'LightsNode';

	}

	/**
	 * Constructs a new lights node.
	 */
	constructor() {

		super( 'vec3' );

		/**
		 * A node representing the total diffuse light.
		 *
		 * @type {Node<vec3>}
		 */
		this.totalDiffuseNode = totalDiffuse;

		/**
		 * A node representing the total specular light.
		 *
		 * @type {Node<vec3>}
		 */
		this.totalSpecularNode = totalSpecular;

		/**
		 * A node representing the outgoing light.
		 *
		 * @type {Node<vec3>}
		 */
		this.outgoingLightNode = outgoingLight;

		/**
		 * An array representing the lights in the scene.
		 *
		 * @private
		 * @type {Array<Light>}
		 */
		this._lights = [];

		/**
		 * `LightsNode` sets this property to `true` by default.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.global = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Overwrites the default {@link Node#customCacheKey} implementation by including
	 * light data into the cache key.
	 *
	 * @return {number} The custom cache key.
	 */
	customCacheKey() {

		const builtinLights = this.getBuiltinLights();

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

			const light = builtinLights[ i ];

			_hashData.push( light.id );
			_hashData.push( light.castShadow ? 1 : 0 );

			if ( light.castShadow === true ) {

				// force rebuild on version change to avoid the usage of removed shadow nodes

				const lightNode = _lightsNodeRef.get( light );
				const lightVersion = ( lightNode ) ? lightNode.version : 0; // light nodes start at version 0, see #35019

				_hashData.push( lightVersion );

			}

			if ( light.isSpotLight === true ) {

				const hashMap = ( light.map !== null ) ? light.map.id : -1;
				const hashColorNode = ( light.colorNode ) ? light.colorNode.getCacheKey() : -1;

				_hashData.push( hashMap, hashColorNode );

			}

		}

		const cacheKey = hashArray( _hashData );

		_hashData.length = 0;

		return cacheKey;

	}

	/**
	 * Computes a hash value for identifying the current light nodes setup.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @return {string} The computed hash.
	 */
	getHash( builder ) {

		const nodeData = builder.getDataFromNode( this );

		if ( nodeData.lightNodesHash === undefined ) {

			const lightNodes = this.setupLightsNode( builder );

			nodeData.lightNodes = lightNodes;

			const hash = [];

			for ( const lightNode of lightNodes ) {

				hash.push( lightNode.getHash() );

			}

			nodeData.lightNodesHash = 'lights-' + hash.join( ',' );

		}

		return nodeData.lightNodesHash;

	}

	/**
	 * Creates lighting nodes for each scene light. This makes it possible to further
	 * process lights in the node system.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @return {Array<LightingNode>} The array of lighting nodes.
	 */
	setupLightsNode( builder ) {

		const nodeData = builder.getDataFromNode( this );
		const lightNodes = [];

		const previousLightNodes = nodeData.lightNodes || null;
		const materialLightings = builder.context.materialLightings;

		const builtinLights = this.getBuiltinLights();

		const lights = sortLights( [ ...materialLightings, ...builtinLights ] );

		for ( const light of lights ) {

			if ( light.isNode ) {

				lightNodes.push( light );

			} else {

				let lightNode = null;

				if ( previousLightNodes !== null ) {

					lightNode = getLightNodeById( light.id, previousLightNodes );

				}

				if ( lightNode === null ) {

					const lightNodeClass = light._lightNode;

					if ( lightNodeClass === undefined ) {

						warn( `LightsNode.setupNodeLights: Light node not found for ${ light.constructor.name }` );
						continue;

					}

					if ( _lightsNodeRef.has( light ) === false ) {

						_lightsNodeRef.set( light, new lightNodeClass( light ) );

					}

					lightNode = _lightsNodeRef.get( light );

				}

				lightNodes.push( lightNode );

			}

		}

		return lightNodes;

	}

	/**
	 * Sets up a direct light in the lighting model.
	 *
	 * @param {Object} builder - The builder object containing the context and stack.
	 * @param {Object} lightNode - The light node.
	 * @param {Object} lightData - The light object containing color and direction properties.
	 */
	setupDirectLight( builder, lightNode, lightData ) {

		const { lightingModel, reflectedLight } = builder.context;

		lightingModel.direct( {
			...lightData,
			lightNode,
			reflectedLight
		}, builder );

	}

	/**
	 * Sets up a direct rect area light in the lighting model.
	 *
	 * @param {Object} builder - The builder object containing the context and stack.
	 * @param {Object} lightNode - The light node.
	 * @param {Object} lightData - The light object containing color and area light properties.
	 */
	setupDirectRectAreaLight( builder, lightNode, lightData ) {

		const { lightingModel, reflectedLight } = builder.context;

		lightingModel.directRectArea( {
			...lightData,
			lightNode,
			reflectedLight
		}, builder );

	}

	/**
	 * Setups the internal lights by building all respective
	 * light nodes.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @param {Array<LightingNode>} lightNodes - An array of lighting nodes.
	 */
	setupLights( builder, lightNodes ) {

		for ( const lightNode of lightNodes ) {

			lightNode.build( builder );

		}

		if ( builder.context.lightProbeIrradiance !== undefined ) {

			builder.context.irradiance.addAssign( builder.context.lightProbeIrradiance );

		}

	}

	getLightNodes( builder ) {

		const nodeData = builder.getDataFromNode( this );

		if ( nodeData.lightNodes === undefined ) {

			nodeData.lightNodes = this.setupLightsNode( builder );

		}

		return nodeData.lightNodes;

	}

	/**
	 * The implementation makes sure that for each light in the scene
	 * there is a corresponding light node. By building the light nodes
	 * and evaluating the lighting model the outgoing light is computed.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @return {Node<vec3>} A node representing the outgoing light.
	 */
	setup( builder ) {

		const currentLightsNode = builder.lightsNode;

		builder.lightsNode = this;

		let outgoingLightNode = this.outgoingLightNode;

		const context = builder.context;
		const lightingModel = context.lightingModel;

		if ( lightingModel ) {

			const { totalDiffuseNode, totalSpecularNode } = this;

			context.outgoingLight = outgoingLightNode;

			builder.addStack();

			lightingModel.start( builder );

			const { backdrop, backdropAlpha } = context;
			const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight;

			let totalDiffuse = directDiffuse.add( indirectDiffuse );

			if ( backdrop !== null ) {

				if ( backdropAlpha !== null ) {

					totalDiffuse = vec3( backdropAlpha.mix( totalDiffuse, backdrop ) );

				} else {

					totalDiffuse = vec3( backdrop );

				}

			}

			totalDiffuseNode.assign( totalDiffuse );
			totalSpecularNode.assign( directSpecular.add( indirectSpecular ) );

			outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) );

			lightingModel.finish( builder );

			outgoingLightNode = outgoingLightNode.bypass( builder.removeStack() );

		}

		builder.lightsNode = currentLightsNode;

		return outgoingLightNode;

	}

	/**
	 * Configures this node with an array of lights.
	 *
	 * @param {Array<Light>} lights - An array of lights.
	 * @return {LightsNode} A reference to this node.
	 */
	setLights( lights ) {

		this._lights = lights;

		return this;

	}

	/**
	 * Returns an array of the scene's lights.
	 *
	 * @return {Array<Light>} The scene's lights.
	 */
	getLights() {

		return this._lights;

	}

	/**
	 * Returns an array of the scene's lights.
	 *
	 * The light variations are shader-dependent;
	 * if this array changes, the shader needs to be recreated.
	 *
	 * @return {Array<Light>} The scene's lights.
	 */
	getBuiltinLights() {

		return this._lights;

	}

	/**
	 * Whether the scene has lights or not.
	 *
	 * @type {boolean}
	 */
	get hasLights() {

		return this._lights.length > 0;

	}

}

/**
 * TSL function for creating an instance of `LightsNode` and configuring
 * it with the given array of lights.
 *
 * @tsl
 * @function
 * @param {Array<Light>} lights - An array of lights.
 * @return {LightsNode} The created lights node.
 */
const lights = ( lights = [] ) => new LightsNode().setLights( lights );

/**
 * Data structure for the renderer. It allows defining values
 * with chained, hierarchical keys. Keys are meant to be
 * objects since the module internally works with Weak Maps
 * for performance reasons.
 *
 * @private
 */
class ChainMap {

	/**
	 * Constructs a new Chain Map.
	 */
	constructor() {

		/**
		 * A map of Weak Maps by their key length.
		 *
		 * @type {Object<number, WeakMap>}
		 */
		this.weakMaps = {};


	}

	/**
	 * Returns the Weak Map for the given keys.
	 *
	 * @param {Array<Object>} keys - List of keys.
	 * @return {WeakMap} The weak map.
	 */
	_getWeakMap( keys ) {

		const length = keys.length;

		let weakMap = this.weakMaps[ length ];

		if ( weakMap === undefined ) {

			weakMap = new WeakMap();
			this.weakMaps[ length ] = weakMap;

		}

		return weakMap;

	}

	/**
	 * Returns the value for the given array of keys.
	 *
	 * @param {Array<Object>} keys - List of keys.
	 * @return {any} The value. Returns `undefined` if no value was found.
	 */
	get( keys ) {

		let map = this._getWeakMap( keys );

		for ( let i = 0; i < keys.length - 1; i ++ ) {

			map = map.get( keys[ i ] );

			if ( map === undefined ) return undefined;

		}

		return map.get( keys[ keys.length - 1 ] );

	}

	/**
	 * Sets the value for the given keys.
	 *
	 * @param {Array<Object>} keys - List of keys.
	 * @param {any} value - The value to set.
	 * @return {ChainMap} A reference to this Chain Map.
	 */
	set( keys, value ) {

		let map = this._getWeakMap( keys );

		for ( let i = 0; i < keys.length - 1; i ++ ) {

			const key = keys[ i ];

			if ( map.has( key ) === false ) map.set( key, new WeakMap() );

			map = map.get( key );

		}

		map.set( keys[ keys.length - 1 ], value );

		return this;

	}

	/**
	 * Deletes a value for the given keys.
	 *
	 * @param {Array<Object>} keys - The keys.
	 * @return {boolean} Returns `true` if the value has been removed successfully and `false` if the value has not be found.
	 */
	delete( keys ) {

		let map = this._getWeakMap( keys );

		for ( let i = 0; i < keys.length - 1; i ++ ) {

			map = map.get( keys[ i ] );

			if ( map === undefined ) return false;

		}

		return map.delete( keys[ keys.length - 1 ] );

	}

}

const _shadowMaterialLib = /*@__PURE__*/ new WeakMap();
const _shadowRenderObjectLibrary = /*@__PURE__*/ new ChainMap();
const _shadowRenderObjectKeys = [];

/**
 * Retrieves or creates a shadow material for the given light source.
 *
 * This function checks if a shadow material already exists for the provided light.
 * If not, it creates a new `NodeMaterial` configured for shadow rendering and stores it
 * in the `shadowMaterialLib` for future use.
 *
 * @private
 * @function
 * @param {Light} light - The light source for which the shadow material is needed.
 *                         If the light is a point light, a depth node is calculated
 *                         using the linear shadow distance.
 * @returns {NodeMaterial} The shadow material associated with the given light.
 */
const _getShadowMaterial = ( light ) => {

	let material = _shadowMaterialLib.get( light );

	if ( material === undefined ) {

		material = new NodeMaterial();
		material.colorNode = vec4( 0, 0, 0, 1 );
		material.isShadowPassMaterial = true; // Use to avoid other overrideMaterial override material.colorNode unintentionally when using material.shadowNode
		material.name = 'ShadowMaterial';
		material.blending = NoBlending;
		material.fog = false;

		_shadowMaterialLib.set( light, material );

	}

	return material;

};

/**
 * Disposes the shadow material for the given light source.
 *
 * @private
 * @param {Light} light - The light source.
 */
const _disposeShadowMaterial = ( light ) => {

	const material = _shadowMaterialLib.get( light );

	if ( material !== undefined ) {

		material.dispose();
		_shadowMaterialLib.delete( light );

	}

};

/**
 * Creates a function to render shadow objects in a scene.
 *
 * @private
 * @function
 * @param {Renderer} renderer - The renderer.
 * @param {LightShadow} shadow - The light shadow object containing shadow properties.
 * @param {number} shadowType - The type of shadow map (e.g., BasicShadowMap).
 * @return {shadowRenderObjectFunction} A function that renders shadow objects.
 */
const _getShadowRenderObjectFunction = ( renderer, shadow, shadowType ) => {

	_shadowRenderObjectKeys[ 0 ] = renderer;
	_shadowRenderObjectKeys[ 1 ] = shadow;

	let renderObjectFunction = _shadowRenderObjectLibrary.get( _shadowRenderObjectKeys );

	if ( renderObjectFunction === undefined || renderObjectFunction.shadowType !== shadowType ) {

		renderObjectFunction = ( object, scene, _camera, geometry, material, group, lightsNode, clippingContext, passId ) => {

			if ( object.castShadow === true || ( object.receiveShadow && shadowType === VSMShadowMap ) ) {

				object.onBeforeShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group );

				renderer.renderObject( object, scene, _camera, geometry, material, group, lightsNode, clippingContext, passId );

				object.onAfterShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group );

			}

		};

		renderObjectFunction.shadowType = shadowType;

		_shadowRenderObjectLibrary.set( _shadowRenderObjectKeys, renderObjectFunction );

	}

	_shadowRenderObjectKeys[ 0 ] = null;
	_shadowRenderObjectKeys[ 1 ] = null;

	return renderObjectFunction;

};

/**
 * Base class for all shadow nodes.
 *
 * Shadow nodes encapsulate shadow related logic and are always coupled to lighting nodes.
 * Lighting nodes might share the same shadow node type or use specific ones depending on
 * their requirements.
 *
 * @augments Node
 */
class ShadowBaseNode extends Node {

	static get type() {

		return 'ShadowBaseNode';

	}

	/**
	 * Constructs a new shadow base node.
	 *
	 * @param {Light} light - The shadow casting light.
	 */
	constructor( light ) {

		super();

		/**
		 * The shadow casting light.
		 *
		 * @type {Light}
		 */
		this.light = light;

		/**
		 * Overwritten since shadows are updated by default per render.
		 *
		 * @type {string}
		 * @default 'render'
		 */
		this.updateBeforeType = NodeUpdateType.RENDER;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isShadowBaseNode = true;

	}

	isCacheable( /*builder*/ ) {

		return false;

	}

	/**
	 * Retrieves or creates a shadow material for the shadow casting light source.
	 *
	 * This method checks if a shadow material already exists for the provided light in the internal library.
	 * If not, it creates a new `NodeMaterial` configured for shadow rendering and stores it for future use.
	 *
	 * @return {NodeMaterial} The shadow material associated with the given light.
	 */
	getShadowMaterial() {

		return _getShadowMaterial( this.light );

	}

	/**
	 * Disposes the shadow material for the shadow casting light source.
	 */
	disposeShadowMaterial() {

		_disposeShadowMaterial( this.light );

	}

	/**
	 * Returns a function to render shadow objects in a scene for the given light shadow and renderer.
	 *
	 * @param {Renderer} renderer - The renderer.
	 * @param {LightShadow} [shadow=this.light.shadow] - The light shadow object containing shadow properties.
	 * @return {Function} A function that renders shadow objects.
	 */
	getShadowRenderObjectFunction( renderer, shadow = this.light.shadow ) {

		return _getShadowRenderObjectFunction( renderer, shadow, renderer.shadowMap.type );

	}

	/**
	 * Setups the shadow position node which is by default the predefined TSL node object `shadowPositionWorld`.
	 *
	 * @param {NodeBuilder} object - A configuration object that must at least hold a material reference.
	 */
	setupShadowPosition( { context, material } ) {

		// Use assign inside an Fn()

		shadowPositionWorld.assign( material.receivedShadowPositionNode || context.shadowPositionWorld || positionWorld );

	}

}

/**
 * TSL object that represents the vertex position in world space during the shadow pass.
 *
 * @tsl
 * @type {Node<vec3>}
 */
const shadowPositionWorld = /*@__PURE__*/ property( 'vec3', 'shadowPositionWorld' );

// the uniforms of the shadows used by the filters. the filters are called for each material, the uniforms are
// created once per shadow so the materials share the uniforms of the render group

const _shadowUniforms = /*@__PURE__*/ new WeakMap();

function getShadowUniforms( shadow ) {

	let uniforms = _shadowUniforms.get( shadow );

	if ( uniforms === undefined ) {

		uniforms = {
			mapSize: reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ),
			radius: reference( 'radius', 'float', shadow ).setGroup( renderGroup )
		};

		_shadowUniforms.set( shadow, uniforms );

	}

	return uniforms;

}

/**
 * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map
 * with a binary `[0,1]` result.
 *
 * @method
 * @param {Object} inputs - The input parameter object.
 * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
 * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
 * @return {Node<float>} The filtering result.
 */
const BasicShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer } ) => {

	let basic = texture( depthTexture, shadowCoord.xy ).setName( 't_basic' );

	if ( depthTexture.isArrayTexture ) {

		basic = basic.depth( depthLayer );

	}

	return basic.compare( shadowCoord.z );

} );

/**
 * A shadow filtering function performing PCF filtering with Vogel disk sampling and IGN.
 *
 * Uses 5 samples distributed via Vogel disk pattern, rotated per-pixel using Interleaved
 * Gradient Noise (IGN) to break up banding artifacts. Combined with hardware PCF (4-tap
 * filtering per sample), this effectively provides 20 filtered taps with better distribution.
 *
 * @method
 * @param {Object} inputs - The input parameter object.
 * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
 * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
 * @param {LightShadow} inputs.shadow - The light shadow.
 * @return {Node<float>} The filtering result.
 */
const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => {

	const depthCompare = ( uv, compare ) => {

		let depth = texture( depthTexture, uv );

		if ( depthTexture.isArrayTexture ) {

			depth = depth.depth( depthLayer );

		}

		return depth.compare( compare );

	};

	const { mapSize, radius } = getShadowUniforms( shadow );

	const texelSize = vec2( 1 ).div( mapSize );
	const radiusScaled = radius.mul( texelSize );

	// Use IGN to rotate sampling pattern per pixel (phi = IGN * 2π)
	const phi = interleavedGradientNoise( screenCoordinate.xy ).mul( 6.28318530718 );

	// 5 samples using Vogel disk distribution
	return add(
		depthCompare( shadowCoord.xy.add( vogelDiskSample( 0, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
		depthCompare( shadowCoord.xy.add( vogelDiskSample( 1, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
		depthCompare( shadowCoord.xy.add( vogelDiskSample( 2, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
		depthCompare( shadowCoord.xy.add( vogelDiskSample( 3, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
		depthCompare( shadowCoord.xy.add( vogelDiskSample( 4, 5, phi ).mul( radiusScaled ) ), shadowCoord.z )
	).mul( 1 / 5 );

} );

/**
 * A shadow filtering function performing VSM filtering.
 *
 * @method
 * @param {Object} inputs - The input parameter object.
 * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
 * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
 * @return {Node<float>} The filtering result.
 */
const VSMShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer }, builder ) => {

	let distribution = texture( depthTexture ).sample( shadowCoord.xy );

	if ( depthTexture.isArrayTexture ) {

		distribution = distribution.depth( depthLayer );

	}

	distribution = distribution.rg;

	const mean = distribution.x;
	const variance = max( 0.0000001, distribution.y.mul( distribution.y ) );

	const hardShadow = ( builder.renderer.reversedDepthBuffer ) ? step( mean, shadowCoord.z ) : step( shadowCoord.z, mean );

	const output = float( 1 ).toVar(); // default, fully lit

	If( hardShadow.notEqual( 1.0 ), () => {

		// Distance from mean
		const d = shadowCoord.z.sub( mean );

		// Chebyshev's inequality for upper bound on probability
		let p_max = variance.div( variance.add( d.mul( d ) ) );

		// Reduce light bleeding by remapping [amount, 1] to [0, 1]
		p_max = clamp( sub( p_max, 0.3 ).div( 0.65 ) );

		output.assign( max( hardShadow, p_max ) );

	} );
	return output;

} );

/**
 * Represents the shader code for the first VSM render pass.
 *
 * @private
 * @method
 * @param {Object} inputs - The input parameter object.
 * @param {Node<float>} inputs.samples - The number of samples
 * @param {Node<float>} inputs.radius - The radius.
 * @param {Node<float>} inputs.size - The size.
 * @param {TextureNode} inputs.shadowPass - A reference to the render target's depth data.
 * @return {Node<vec2>} The VSM output.
 */
const VSMPassVertical = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass, depthLayer } ) => {

	const mean = float( 0 ).toVar( 'meanVertical' );
	const squaredMean = float( 0 ).toVar( 'squareMeanVertical' );

	const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
	const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( -1 ) );

	Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {

		const uvOffset = uvStart.add( float( i ).mul( uvStride ) );

		let depth = shadowPass.sample( add( screenCoordinate.xy, vec2( 0, uvOffset ).mul( radius ) ).div( size ) );

		if ( shadowPass.value.isArrayTexture ) {

			depth = depth.depth( depthLayer );

		}

		depth = depth.x;

		mean.addAssign( depth );
		squaredMean.addAssign( depth.mul( depth ) );

	} );

	mean.divAssign( samples );
	squaredMean.divAssign( samples );

	const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ).max( 0 ) );
	return vec2( mean, std_dev );

} );

/**
 * Represents the shader code for the second VSM render pass.
 *
 * @private
 * @method
 * @param {Object} inputs - The input parameter object.
 * @param {Node<float>} inputs.samples - The number of samples
 * @param {Node<float>} inputs.radius - The radius.
 * @param {Node<float>} inputs.size - The size.
 * @param {TextureNode} inputs.shadowPass - The result of the first VSM render pass.
 * @return {Node<vec2>} The VSM output.
 */
const VSMPassHorizontal = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass, depthLayer } ) => {

	const mean = float( 0 ).toVar( 'meanHorizontal' );
	const squaredMean = float( 0 ).toVar( 'squareMeanHorizontal' );

	const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
	const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( -1 ) );

	Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {

		const uvOffset = uvStart.add( float( i ).mul( uvStride ) );

		let distribution = shadowPass.sample( add( screenCoordinate.xy, vec2( uvOffset, 0 ).mul( radius ) ).div( size ) );

		if ( shadowPass.value.isArrayTexture ) {

			distribution = distribution.depth( depthLayer );

		}

		mean.addAssign( distribution.x );
		squaredMean.addAssign( add( distribution.y.mul( distribution.y ), distribution.x.mul( distribution.x ) ) );

	} );

	mean.divAssign( samples );
	squaredMean.divAssign( samples );

	const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ).max( 0 ) );
	return vec2( mean, std_dev );

} );

const _shadowFilterLib = [ BasicShadowFilter, PCFShadowFilter, null /* PCFSoftShadowMap, removed */, VSMShadowFilter ];

//

let _rendererState;
const _quadMesh = /*@__PURE__*/ new QuadMesh();

/**
 * Represents the default shadow implementation for lighting nodes.
 *
 * @augments ShadowBaseNode
 */
class ShadowNode extends ShadowBaseNode {

	static get type() {

		return 'ShadowNode';

	}

	/**
	 * Constructs a new shadow node.
	 *
	 * @param {Light} light - The shadow casting light.
	 * @param {?LightShadow} [shadow=null] - An optional light shadow.
	 */
	constructor( light, shadow = null ) {

		super( light );

		/**
		 * The light shadow which defines the properties light's
		 * shadow.
		 *
		 * @type {?LightShadow}
		 * @default null
		 */
		this.shadow = shadow || light.shadow;

		/**
		 * A reference to the shadow map which is a render target.
		 *
		 * @type {?RenderTarget}
		 * @default null
		 */
		this.shadowMap = null;

		/**
		 * Only relevant for VSM shadows. Render target for the
		 * first VSM render pass.
		 *
		 * @type {?RenderTarget}
		 * @default null
		 */
		this.vsmShadowMapVertical = null;

		/**
		 * Only relevant for VSM shadows. Render target for the
		 * second VSM render pass.
		 *
		 * @type {?RenderTarget}
		 * @default null
		 */
		this.vsmShadowMapHorizontal = null;

		/**
		 * Only relevant for VSM shadows. Node material which
		 * is used to render the first VSM pass.
		 *
		 * @type {?NodeMaterial}
		 * @default null
		 */
		this.vsmMaterialVertical = null;

		/**
		 * Only relevant for VSM shadows. Node material which
		 * is used to render the second VSM pass.
		 *
		 * @type {?NodeMaterial}
		 * @default null
		 */
		this.vsmMaterialHorizontal = null;

		/**
		 * A reference to the output node which defines the
		 * final result of this shadow node.
		 *
		 * @type {?Node}
		 * @private
		 * @default null
		 */
		this._node = null;

		/**
		 * The current shadow map type of this shadow node.
		 *
		 * @type {?number}
		 * @private
		 * @default null
		 */
		this._currentShadowType = null;

		/**
		 * A Weak Map holding the current frame ID per camera. Used
		 * to control the update of shadow maps.
		 *
		 * @type {WeakMap<Camera,number>}
		 * @private
		 */
		this._cameraFrameId = new WeakMap();

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isShadowNode = true;

		/**
		 * This index can be used when overriding setupRenderTarget with a RenderTarget Array to specify the depth layer.
		 *
		 * @type {number}
		 * @readonly
		 * @default 0
		 */
		this.depthLayer = 0;

	}

	/**
	 * Setups the shadow filtering.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @param {Object} inputs - A configuration object that defines the shadow filtering.
	 * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF.
	 * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
	 * @param {Node<vec3>} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map.
	 * @param {LightShadow} inputs.shadow - The light shadow.
	 * @return {Node<float>} The result node of the shadow filtering.
	 */
	setupShadowFilter( builder, { filterFn, depthTexture, shadowCoord, shadow, depthLayer } ) {

		const frustumTest = shadowCoord.x.greaterThanEqual( 0 )
			.and( shadowCoord.x.lessThanEqual( 1 ) )
			.and( shadowCoord.y.greaterThanEqual( 0 ) )
			.and( shadowCoord.y.lessThanEqual( 1 ) )
			.and( shadowCoord.z.lessThanEqual( 1 ) );

		const shadowNode = filterFn( { depthTexture, shadowCoord, shadow, depthLayer } );

		return frustumTest.select( shadowNode, float( 1 ) );

	}

	/**
	 * Setups the shadow coordinates.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @param {Node<vec3>} shadowPosition - A node representing the shadow position.
	 * @return {Node<vec3>} The shadow coordinates.
	 */
	setupShadowCoord( builder, shadowPosition ) {

		const { shadow } = this;
		const { renderer } = builder;

		const bias = shadow.biasNode || reference( 'bias', 'float', shadow ).setGroup( renderGroup );

		let shadowCoord = shadowPosition;
		let coordZ;

		if ( shadow.camera.isOrthographicCamera || renderer.logarithmicDepthBuffer !== true ) {

			shadowCoord = shadowCoord.xyz.div( shadowCoord.w );

			coordZ = shadowCoord.z;

		} else {

			const w = shadowCoord.w;
			shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z

			// The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get
			// updated to use the shadow camera. So, we have to declare our own "local" ones here.
			// TODO: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here?
			const cameraNearLocal = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup );
			const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup );

			coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal );

		}

		shadowCoord = vec3(
			shadowCoord.x,
			shadowCoord.y.oneMinus(), // follow webgpu standards
			renderer.reversedDepthBuffer ? coordZ.sub( bias ) : coordZ.add( bias )
		);

		return shadowCoord;

	}

	/**
	 * Returns the shadow filtering function for the given shadow type.
	 *
	 * @param {number} type - The shadow type.
	 * @return {Function} The filtering function.
	 */
	getShadowFilterFn( type ) {

		return _shadowFilterLib[ type ];

	}


	setupRenderTarget( shadow, builder ) {

		const depthTexture = new DepthTexture( shadow.mapSize.width, shadow.mapSize.height );
		depthTexture.name = 'ShadowDepthTexture';
		depthTexture.compareFunction = builder.renderer.reversedDepthBuffer ? GreaterEqualCompare : LessEqualCompare;

		const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
		shadowMap.texture.name = 'ShadowMap';
		shadowMap.texture.type = shadow.mapType;
		shadowMap.depthTexture = depthTexture;

		return { shadowMap, depthTexture };

	}

	/**
	 * Setups the shadow output node.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @return {Node<vec3>} The shadow output node.
	 */
	setupShadow( builder ) {

		const { renderer, camera } = builder;

		const { light, shadow } = this;

		const { depthTexture, shadowMap } = this.setupRenderTarget( shadow, builder );

		const shadowMapType = renderer.shadowMap.type;
		const hasTextureCompare = renderer.hasCompatibility( Compatibility.TEXTURE_COMPARE );

		if ( shadowMapType === PCFShadowMap && hasTextureCompare ) {

			depthTexture.minFilter = LinearFilter;
			depthTexture.magFilter = LinearFilter;

		} else {

			depthTexture.minFilter = NearestFilter;
			depthTexture.magFilter = NearestFilter;

		}

		shadow.camera.coordinateSystem = camera.coordinateSystem;
		shadow.camera._reversedDepth = renderer.reversedDepthBuffer;
		shadow.camera.updateProjectionMatrix();

		// VSM

		if ( shadowMapType === VSMShadowMap && shadow.isPointLightShadow !== true ) {

			depthTexture.compareFunction = null; // VSM does not use textureSampleCompare()/texture2DCompare()

			if ( shadowMap.depth > 1 ) {

				if ( ! shadowMap._vsmShadowMapVertical ) {

					shadowMap._vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depth: shadowMap.depth, depthBuffer: false } );
					shadowMap._vsmShadowMapVertical.texture.name = 'VSMVertical';

				}

				this.vsmShadowMapVertical = shadowMap._vsmShadowMapVertical;

				if ( ! shadowMap._vsmShadowMapHorizontal ) {

					shadowMap._vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depth: shadowMap.depth, depthBuffer: false } );
					shadowMap._vsmShadowMapHorizontal.texture.name = 'VSMHorizontal';

				}

				this.vsmShadowMapHorizontal = shadowMap._vsmShadowMapHorizontal;

			} else {

				this.vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depthBuffer: false } );
				this.vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depthBuffer: false } );

			}


			let shadowPassVertical = texture( depthTexture );

			if ( depthTexture.isArrayTexture ) {

				shadowPassVertical = shadowPassVertical.depth( this.depthLayer );

			}

			let shadowPassHorizontal = texture( this.vsmShadowMapVertical.texture );

			if ( depthTexture.isArrayTexture ) {

				shadowPassHorizontal = shadowPassHorizontal.depth( this.depthLayer );

			}

			const samples = reference( 'blurSamples', 'float', shadow ).setGroup( renderGroup );
			const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
			const size = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );

			const sharedContext = context( builder.getSharedContext() );

			let material = this.vsmMaterialVertical || ( this.vsmMaterialVertical = new NodeMaterial() );
			material.contextNode = sharedContext;
			material.fragmentNode = VSMPassVertical( { samples, radius, size, shadowPass: shadowPassVertical, depthLayer: this.depthLayer } );
			material.name = 'VSMVertical';

			material = this.vsmMaterialHorizontal || ( this.vsmMaterialHorizontal = new NodeMaterial() );
			material.contextNode = sharedContext;
			material.fragmentNode = VSMPassHorizontal( { samples, radius, size, shadowPass: shadowPassHorizontal, depthLayer: this.depthLayer } );
			material.name = 'VSMHorizontal';

		}

		//

		const shadowIntensity = reference( 'intensity', 'float', shadow ).setGroup( renderGroup );
		const normalBias = reference( 'normalBias', 'float', shadow ).setGroup( renderGroup );

		const shadowMatrix = lightShadowMatrix( light );
		const shadowNormalBias = normalWorld.mul( normalBias );

		let shadowPosition;

		if ( ! renderer.highPrecision || builder.material.receivedShadowPositionNode || builder.context.shadowPositionWorld ) {

			shadowPosition = shadowMatrix.mul( shadowPositionWorld.add( shadowNormalBias ) );

		} else {

			const highpShadowModelMatrix = uniform( 'mat4' ).onObjectUpdate( ( { object }, self ) => {

				return self.value.multiplyMatrices( shadowMatrix.value, object.matrixWorld );

			} );

			shadowPosition = highpShadowModelMatrix.mul( positionLocal ).add( shadowMatrix.mul( vec4( shadowNormalBias, 0 ) ) );

		}

		const shadowCoord = this.setupShadowCoord( builder, shadowPosition );

		//

		const filterFn = shadow.filterNode || this.getShadowFilterFn( renderer.shadowMap.type ) || null;

		if ( filterFn === null ) {

			throw new Error( 'THREE.WebGPURenderer: Shadow map type not supported yet.' );

		}

		const shadowDepthTexture = ( shadowMapType === VSMShadowMap && shadow.isPointLightShadow !== true ) ? this.vsmShadowMapHorizontal.texture : depthTexture;

		const shadowNode = this.setupShadowFilter( builder, { filterFn, shadowTexture: shadowMap.texture, depthTexture: shadowDepthTexture, shadowCoord, shadow, depthLayer: this.depthLayer } );

		let shadowColor;

		if ( renderer.shadowMap.transmitted === true ) {

			if ( shadowMap.texture.isCubeTexture ) {

				// For cube shadow maps (point lights), use cubeTexture with vec3 coordinates
				shadowColor = cubeTexture( shadowMap.texture, shadowCoord.xyz );

			} else {

				shadowColor = texture( shadowMap.texture, shadowCoord );

				if ( depthTexture.isArrayTexture ) {

					shadowColor = shadowColor.depth( this.depthLayer );

				}

			}

		}

		//

		let shadowOutput;

		if ( shadowColor ) {

			shadowOutput = mix( 1, shadowNode.rgb.mix( shadowColor, 1 ), shadowIntensity.mul( shadowColor.a ) ).toVar();

		} else {

			shadowOutput = mix( 1, shadowNode, shadowIntensity ).toVar();

		}

		this.shadowMap = shadowMap;
		this.shadow.map = shadowMap;
		this.shadow.needsUpdate = true;

		// Shadow Output + Inspector

		const inspectName = `${ this.light.type } Shadow [ ${ this.light.name || 'ID: ' + this.light.id } ]`;

		if ( shadowColor ) {

			shadowColor.toInspector( `${ inspectName } / Color`, () => {

				if ( this.shadowMap.texture.isCubeTexture ) {

					return cubeTexture( this.shadowMap.texture, equirectDirection() );

				}

				return texture( this.shadowMap.texture );

			} );

		}

		return shadowOutput.toInspector( `${ inspectName } / Depth`, () => {

			const shadowCameraNear = reference( 'near', 'float', this.shadow.camera );
			const shadowCameraFar = reference( 'far', 'float', this.shadow.camera );

			let depthNode;

			if ( this.shadowMap.texture.isCubeTexture ) {

				depthNode = cubeTexture( this.shadowMap.depthTexture, equirectDirection() ).r;

			} else {

				depthNode = texture( this.shadowMap.depthTexture ).r;

			}

			let linearDepth;

			if ( this.shadow.camera.isPerspectiveCamera ) {

				linearDepth = perspectiveDepthToViewZ( depthNode, shadowCameraNear, shadowCameraFar );

			} else {

				linearDepth = orthographicDepthToViewZ( depthNode, shadowCameraNear, shadowCameraFar );

			}

			linearDepth = viewZToOrthographicDepth( linearDepth, shadowCameraNear, shadowCameraFar );

			return linearDepth.oneMinus();

		} );

	}

	/**
	 * The implementation performs the setup of the output node. An output is only
	 * produces if shadow mapping is globally enabled in the renderer.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @return {ShaderCallNodeInternal} The output node.
	 */
	setup( builder ) {

		if ( builder.renderer.shadowMap.enabled === false ) return;

		return Fn( () => {

			const currentShadowType = builder.renderer.shadowMap.type;

			if ( this._currentShadowType !== currentShadowType ) {

				this._reset();
				this._node = null;

			}

			let node = this._node;

			this.setupShadowPosition( builder );

			if ( node === null ) {

				this._node = node = this.setupShadow( builder );
				this._currentShadowType = currentShadowType;

			}

			if ( builder.material.receivedShadowNode ) {

				node = builder.material.receivedShadowNode( node );

			}

			return node;

		} )();

	}

	/**
	 * Renders the shadow. The logic of this function could be included
	 * into {@link ShadowNode#updateShadow} however more specialized shadow
	 * nodes might require a custom shadow map rendering. By having a
	 * dedicated method, it's easier to overwrite the default behavior.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	renderShadow( frame ) {

		const { shadow, shadowMap, light } = this;
		const { renderer, scene } = frame;

		shadow.updateMatrices( light );

		shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.height, shadowMap.depth );

		const currentSceneName = scene.name;

		scene.name = `Shadow Map [ ${ light.name || 'ID: ' + light.id } ]`;

		renderer.render( scene, shadow.camera );

		scene.name = currentSceneName;

	}

	/**
	 * Updates the shadow.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	updateShadow( frame ) {

		const { shadowMap, shadow } = this;
		const { renderer, scene, camera } = frame;

		const shadowType = renderer.shadowMap.type;

		const _shadowCameraLayer = shadow.camera.layers.mask;

		if ( ( shadow.camera.layers.mask & 0xFFFFFFFE ) === 0 ) {

			shadow.camera.layers.mask = camera.layers.mask;

		}

		const currentRenderObjectFunction = renderer.getRenderObjectFunction();

		_rendererState = resetRendererAndSceneState( renderer, scene, _rendererState );

		scene.overrideMaterial = this.getShadowMaterial();

		renderer.setRenderObjectFunction( this.getShadowRenderObjectFunction( renderer, this.shadow || this.light.shadow ) );

		renderer.setClearColor( 0x000000, 0 );

		renderer.setRenderTarget( shadowMap );

		const currentMatrixWorldAutoUpdate = scene.matrixWorldAutoUpdate;

		// The scene was already updated by the enclosing render.
		scene.matrixWorldAutoUpdate = false;

		this.renderShadow( frame );

		scene.matrixWorldAutoUpdate = currentMatrixWorldAutoUpdate;

		renderer.setRenderObjectFunction( currentRenderObjectFunction );

		// vsm blur pass

		if ( shadowType === VSMShadowMap && shadow.isPointLightShadow !== true ) {

			this.vsmPass( renderer );

		}

		shadow.camera.layers.mask = _shadowCameraLayer;

		restoreRendererAndSceneState( renderer, scene, _rendererState );

	}

	/**
	 * For VSM additional render passes are required.
	 *
	 * @param {Renderer} renderer - A reference to the current renderer.
	 */
	vsmPass( renderer ) {

		const { shadow } = this;

		const depth = this.shadowMap.depth;
		this.vsmShadowMapVertical.setSize( shadow.mapSize.width, shadow.mapSize.height, depth );
		this.vsmShadowMapHorizontal.setSize( shadow.mapSize.width, shadow.mapSize.height, depth );

		renderer.setRenderTarget( this.vsmShadowMapVertical );
		_quadMesh.material = this.vsmMaterialVertical;
		_quadMesh.render( renderer );

		renderer.setRenderTarget( this.vsmShadowMapHorizontal );
		_quadMesh.material = this.vsmMaterialHorizontal;
		_quadMesh.render( renderer );

	}

	/**
	 * Frees the internal resources of this shadow node.
	 */
	dispose() {

		this._reset();

		super.dispose();

	}

	/**
	 * Resets the resouce state of this shadow node.
	 *
	 * @private
	 */
	_reset() {

		this._currentShadowType = null;

		this.disposeShadowMaterial();

		if ( this.shadowMap ) {

			this.shadowMap.dispose();
			this.shadowMap = null;

		}

		if ( this.vsmShadowMapVertical !== null ) {

			this.vsmShadowMapVertical.dispose();
			this.vsmShadowMapVertical = null;

			this.vsmMaterialVertical.dispose();
			this.vsmMaterialVertical = null;

		}

		if ( this.vsmShadowMapHorizontal !== null ) {

			this.vsmShadowMapHorizontal.dispose();
			this.vsmShadowMapHorizontal = null;

			this.vsmMaterialHorizontal.dispose();
			this.vsmMaterialHorizontal = null;

		}

	}

	/**
	 * The implementation performs the update of the shadow map if necessary.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	updateBefore( frame ) {

		// do not render shadow maps during precompilation

		if ( frame.renderer._precompilationState !== null ) return;

		const { shadow } = this;

		let needsUpdate = shadow.needsUpdate || shadow.autoUpdate;

		if ( needsUpdate ) {

			if ( this._cameraFrameId.get( frame.camera ) === frame.frameId ) {

				needsUpdate = false;

			}

			this._cameraFrameId.set( frame.camera, frame.frameId );

		}

		if ( needsUpdate ) {

			this.updateShadow( frame );

			shadow.needsUpdate = false;

		}

	}

}

/**
 * TSL function for creating an instance of `ShadowNode`.
 *
 * @tsl
 * @function
 * @param {Light} light - The shadow casting light.
 * @param {?LightShadow} [shadow] - The light shadow.
 * @return {ShadowNode} The created shadow node.
 */
const shadow = ( light, shadow ) => new ShadowNode( light, shadow );

const _clearColor$1 = /*@__PURE__*/ new Color();
const _projScreenMatrix = /*@__PURE__*/ new Matrix4();
const _lightPositionWorld = /*@__PURE__*/ new Vector3();
const _lookTarget = /*@__PURE__*/ new Vector3();

// Cube map face directions and up vectors for point light shadows
// Face order: +X, -X, +Y, -Y, +Z, -Z
// WebGPU coordinate system - same face orientations as CubeCamera
const _cubeDirectionsWebGPU = [
	/*@__PURE__*/ new Vector3( -1, 0, 0 ), /*@__PURE__*/ new Vector3( 1, 0, 0 ), /*@__PURE__*/ new Vector3( 0, 1, 0 ),
	/*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, 1 ), /*@__PURE__*/ new Vector3( 0, 0, -1 )
];

const _cubeUpsWebGPU = [
	/*@__PURE__*/ new Vector3( 0, 1, 0 ), /*@__PURE__*/ new Vector3( 0, 1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, -1 ),
	/*@__PURE__*/ new Vector3( 0, 0, 1 ), /*@__PURE__*/ new Vector3( 0, 1, 0 ), /*@__PURE__*/ new Vector3( 0, 1, 0 )
];

// WebGL coordinate system - standard OpenGL convention
const _cubeDirectionsWebGL = [
	/*@__PURE__*/ new Vector3( 1, 0, 0 ), /*@__PURE__*/ new Vector3( -1, 0, 0 ), /*@__PURE__*/ new Vector3( 0, 1, 0 ),
	/*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, 1 ), /*@__PURE__*/ new Vector3( 0, 0, -1 )
];

const _cubeUpsWebGL = [
	/*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, 1 ),
	/*@__PURE__*/ new Vector3( 0, 0, -1 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, -1, 0 )
];

/**
 * Returns a function that gives the depth the cube shadow map stores for the point at
 * `direction * distance`, from the largest absolute component of the direction. Each face
 * stores the depth along its own axis, and perspective depth is `depthA + depthB / max( |x|, |y|, |z| )`,
 * so everything but the division is computed once.
 *
 * @private
 * @param {Node<float>} distance - The distance from the light to the fragment.
 * @param {Node<float>} near - The near plane of the shadow camera.
 * @param {Node<float>} far - The far plane of the shadow camera.
 * @param {Node<float>} bias - The shadow bias.
 * @param {Renderer} renderer - The renderer.
 * @return {function(Node<float>):Node<float>} The function, which takes the largest absolute component of the direction.
 */
const getPointShadowDepthFn = ( distance, near, far, bias, renderer ) => {

	const { reversedDepthBuffer, logarithmicDepthBuffer } = renderer;

	// logarithmic depth gets a separate depth function

	if ( logarithmicDepthBuffer === true ) {

		return ( maxAbs ) => viewZToLogarithmicDepth( maxAbs.mul( distance ).negate(), near, far ).add( bias );

	}

	// default and reverse depth share one

	const depthRange = far.sub( near );
	const nearFar = near.mul( far ).div( depthRange.mul( distance ) );

	let depthA, depthB;

	if ( reversedDepthBuffer === true ) {

		depthA = near.div( depthRange ).add( bias ).negate().toConst();
		depthB = nearFar.toConst();

	} else {

		depthA = far.div( depthRange ).add( bias ).toConst();
		depthB = nearFar.negate().toConst();

	}

	return ( maxAbs ) => depthA.add( depthB.div( maxAbs ) );

};

const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) => {

	return cubeTexture( depthTexture, bd3D ).compare( dp );

} );

/**
 * A shadow filtering function for point lights using Vogel disk sampling and IGN.
 *
 * Uses 5 samples distributed via Vogel disk pattern in tangent space around the
 * sample direction, rotated per-pixel using Interleaved Gradient Noise (IGN).
 *
 * @method
 * @param {Object} inputs - The input parameter object.
 * @param {CubeDepthTexture} inputs.depthTexture - A reference to the shadow cube map.
 * @param {Node<vec3>} inputs.bd3D - The normalized direction from light to fragment.
 * @param {Node<float>} inputs.distance - The distance from the light to the fragment.
 * @param {LightShadow} inputs.shadow - The light shadow.
 * @return {Node<float>} The filtering result.
 */
const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, distance, shadow }, builder ) => {

	const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
	const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
	const near = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup );
	const far = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup );
	const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );

	const texelSize = radius.div( mapSize.x ).toConst();

	// Build a tangent-space coordinate system for applying offsets
	const absDir = abs( bd3D );
	const tangent = normalize( cross( bd3D, absDir.x.greaterThan( absDir.z ).select( vec3( 0, 1, 0 ), vec3( 1, 0, 0 ) ) ) ).toConst();
	const bitangent = cross( bd3D, tangent ).toConst();

	// Use IGN to rotate sampling pattern per pixel (phi = IGN * 2π)
	const phi = interleavedGradientNoise( screenCoordinate.xy ).mul( 6.28318530718 ).toConst();

	// Each sample is compared against the depth of the point next to the fragment in its direction,
	// on whichever face of the cube it falls, so the comparison is the same on every face.
	const getDepth = getPointShadowDepthFn( distance, near, far, bias, builder.renderer );

	// Accumulate five samples using Vogel disk distribution in tangent space.

	const result = float( 0.0 ).toVar();

	Loop( 5, ( { i } ) => {

		const sample = vogelDiskSample( i, 5, phi );
		const direction = bd3D.add( tangent.mul( sample.x ).add( bitangent.mul( sample.y ) ).mul( texelSize ) ).toConst();
		const absDirection = abs( direction ).toConst();
		const maxAbs = max( absDirection.x, absDirection.y, absDirection.z ).toConst();

		result.addAssign( cubeTexture( depthTexture, direction ).compare( getDepth( maxAbs ) ) );

	} );

	return result.mul( 1.0 / 5.0 );

} );

const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCoord, shadow }, builder ) => {

	// for point lights, the uniform @vShadowCoord is re-purposed to hold
	// the vector from the light to the world-space position of the fragment.
	const shadowPosition = shadowCoord.xyz.toConst();
	const shadowPositionAbs = shadowPosition.abs().toConst();
	const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z );

	const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near );
	const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far );
	const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );

	const result = float( 1.0 ).toVar();

	If( viewZ.sub( shadowCameraFar ).lessThanEqual( 0.0 ).and( viewZ.sub( shadowCameraNear ).greaterThanEqual( 0.0 ) ), () => {

		// bd3D = base direction 3D (direction from light to fragment)
		const bd3D = shadowPosition.normalize();
		const distance = shadowPosition.length();

		const getDepth = getPointShadowDepthFn( distance, shadowCameraNear, shadowCameraFar, bias, builder.renderer );
		const dp = getDepth( viewZ.div( distance ) );

		// percentage-closer filtering using cube texture sampling
		result.assign( filterFn( { depthTexture, bd3D, dp, distance, shadow } ) );

	} );

	return result;

} );

/**
 * Represents the shadow implementation for point light nodes.
 *
 * @augments ShadowNode
 */
class PointShadowNode extends ShadowNode {

	static get type() {

		return 'PointShadowNode';

	}

	/**
	 * Constructs a new point shadow node.
	 *
	 * @param {PointLight} light - The shadow casting point light.
	 * @param {?PointLightShadow} [shadow=null] - An optional point light shadow.
	 */
	constructor( light, shadow = null ) {

		super( light, shadow );

	}

	/**
	 * Overwrites the default implementation to return point light shadow specific
	 * filtering functions.
	 *
	 * @param {number} type - The shadow type.
	 * @return {Function} The filtering function.
	 */
	getShadowFilterFn( type ) {

		return type === BasicShadowMap ? BasicPointShadowFilter : PointShadowFilter;

	}

	/**
	 * Overwrites the default implementation so the unaltered shadow position is used.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @param {Node<vec3>} shadowPosition - A node representing the shadow position.
	 * @return {Node<vec3>} The shadow coordinates.
	 */
	setupShadowCoord( builder, shadowPosition ) {

		return shadowPosition;

	}

	/**
	 * Overwrites the default implementation to only use point light specific
	 * shadow filter functions.
	 *
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @param {Object} inputs - A configuration object that defines the shadow filtering.
	 * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF.
	 * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's depth texture.
	 * @param {Node<vec3>} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map.
	 * @param {LightShadow} inputs.shadow - The light shadow.
	 * @return {Node<float>} The result node of the shadow filtering.
	 */
	setupShadowFilter( builder, { filterFn, depthTexture, shadowCoord, shadow } ) {

		return pointShadowFilter( { filterFn, depthTexture, shadowCoord, shadow } );

	}

	/**
	 * Overwrites the default implementation to create a CubeRenderTarget with CubeDepthTexture.
	 *
	 * @param {LightShadow} shadow - The light shadow object.
	 * @param {NodeBuilder} builder - A reference to the current node builder.
	 * @return {Object} An object containing the shadow map and depth texture.
	 */
	setupRenderTarget( shadow, builder ) {

		const depthTexture = new CubeDepthTexture( shadow.mapSize.width );
		depthTexture.name = 'PointShadowDepthTexture';
		depthTexture.compareFunction = builder.renderer.reversedDepthBuffer ? GreaterEqualCompare : LessEqualCompare;

		const shadowMap = builder.createCubeRenderTarget( shadow.mapSize.width );
		shadowMap.texture.name = 'PointShadowMap';
		shadowMap.depthTexture = depthTexture;

		return { shadowMap, depthTexture };

	}

	/**
	 * Overwrites the default implementation with point light specific
	 * rendering code.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	renderShadow( frame ) {

		const { shadow, shadowMap, light } = this;
		const { renderer, scene } = frame;

		const camera = shadow.camera;
		const shadowMatrix = shadow.matrix;

		// Select cube directions/ups based on coordinate system
		const isWebGPU = renderer.coordinateSystem === WebGPUCoordinateSystem;
		const cubeDirections = isWebGPU ? _cubeDirectionsWebGPU : _cubeDirectionsWebGL;
		const cubeUps = isWebGPU ? _cubeUpsWebGPU : _cubeUpsWebGL;

		shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.width );

		//

		const previousAutoClear = renderer.autoClear;

		const previousClearColor = renderer.getClearColor( _clearColor$1 );
		const previousClearAlpha = renderer.getClearAlpha();

		renderer.autoClear = false;
		renderer.setClearColor( shadow.clearColor, shadow.clearAlpha );

		// Render each cube face
		for ( let face = 0; face < 6; face ++ ) {

			// Set render target to the specific cube face
			renderer.setRenderTarget( shadowMap, face );
			renderer.clear();

			// Update shadow camera matrices for this face

			const far = light.distance || camera.far;

			if ( far !== camera.far ) {

				camera.far = far;
				camera.updateProjectionMatrix();

			}

			_lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
			camera.position.copy( _lightPositionWorld );

			_lookTarget.copy( camera.position );
			_lookTarget.add( cubeDirections[ face ] );
			camera.up.copy( cubeUps[ face ] );
			camera.lookAt( _lookTarget );
			camera.updateMatrixWorld();

			shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z );

			_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
			shadow._frustum.setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth );

			//

			const currentSceneName = scene.name;

			scene.name = `Point Light Shadow [ ${ light.name || 'ID: ' + light.id } ] - Face ${ face + 1 }`;

			renderer.render( scene, camera );

			scene.name = currentSceneName;

		}

		//

		renderer.autoClear = previousAutoClear;
		renderer.setClearColor( previousClearColor, previousClearAlpha );

	}

}

/**
 * TSL function for creating an instance of `PointShadowNode`.
 *
 * @tsl
 * @function
 * @param {PointLight} light - The shadow casting point light.
 * @param {?PointLightShadow} [shadow=null] - An optional point light shadow.
 * @return {PointShadowNode} The created point shadow node.
 */
const pointShadow = ( light, shadow ) => new PointShadowNode( light, shadow );

/**
 * Base class for analytic light nodes.
 *
 * @augments LightingNode
 */
class AnalyticLightNode extends LightingNode {

	static get type() {

		return 'AnalyticLightNode';

	}

	/**
	 * Constructs a new analytic light node.
	 *
	 * @param {?Light} [light=null] - The light source.
	 */
	constructor( light = null ) {

		super();

		/**
		 * The light source.
		 *
		 * @type {?Light}
		 * @default null
		 */
		this.light = light;

		/**
		 * The light's color value.
		 *
		 * @type {Color}
		 */
		this.color = new Color();

		/**
		 * The light's color node. Points to `colorNode` of the light source, if set. Otherwise
		 * it creates a uniform node based on {@link AnalyticLightNode#color}.
		 *
		 * @type {Node}
		 */
		this.colorNode = ( light && light.colorNode ) || uniform( this.color ).setGroup( renderGroup );

		/**
		 * This property is used to retain a reference to the original value of {@link AnalyticLightNode#colorNode}.
		 * The final color node is represented by a different node when using shadows.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.baseColorNode = null;

		/**
		 * Represents the light's shadow.
		 *
		 * @type {?ShadowNode}
   		 * @default null
		 */
		this.shadowNode = null;

		/**
		 * Represents the light's shadow color.
		 *
		 * @type {?Node}
   		 * @default null
		 */
		this.shadowColorNode = null;

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isAnalyticLightNode = true;

		/**
		 * Overwritten since analytic light nodes are updated
		 * once per frame.
		 *
		 * @type {string}
		 * @default 'frame'
		 */
		this.updateType = NodeUpdateType.FRAME;

		if ( light && light.shadow ) {

			this._shadowDisposeListener = () => {

				this.disposeShadow();

			};

			light.addEventListener( 'dispose', this._shadowDisposeListener );

		}

	}

	dispose() {

		if ( this._shadowDisposeListener ) {

			this.light.removeEventListener( 'dispose', this._shadowDisposeListener );

		}

		super.dispose();

	}

	/**
	 * Frees internal resources related to shadows.
	 */
	disposeShadow() {

		if ( this.shadowNode === null ) return;

		this.shadowNode.dispose();
		this.shadowNode = null;
		this.shadowColorNode = null;

		this.colorNode = this.baseColorNode;
		this.baseColorNode = null;

		this.needsUpdate = true;

	}

	getHash() {

		return this.light.uuid;

	}

	/**
	 * Returns a node representing a direction vector which points from the current
	 * position in view space to the light's position in view space.
	 *
	 * @param {NodeBuilder} builder - The builder object used for setting up the light.
	 * @return {Node<vec3>} The light vector node.
	 */
	getLightVector( builder ) {

		return lightViewPosition( this.light ).sub( builder.context.positionView || positionView );

	}

	/**
	 * Sets up the direct lighting for the analytic light node.
	 *
	 * @abstract
	 * @param {NodeBuilder} builder - The builder object used for setting up the light.
	 * @return {Object|undefined} The direct light data (color and direction).
	 */
	setupDirect( /*builder*/ ) { }

	/**
	 * Sets up the direct rect area lighting for the analytic light node.
	 *
	 * @abstract
	 * @param {NodeBuilder} builder - The builder object used for setting up the light.
	 * @return {Object|undefined} The direct rect area light data.
	 */
	setupDirectRectArea( /*builder*/ ) { }

	/**
	 * Setups the shadow node for this light. The method exists so concrete light classes
	 * can setup different types of shadow nodes.
	 *
	 * @return {ShadowNode} The created shadow node.
	 */
	setupShadowNode() {

		return shadow( this.light );

	}

	/**
	 * Setups the shadow for this light. This method is only executed if the light
	 * cast shadows and the current build object receives shadows. It incorporates
	 * shadows into the lighting computation.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setupShadow( builder ) {

		const { renderer } = builder;

		if ( renderer.shadowMap.enabled === false ) return;

		let shadowColorNode = this.shadowColorNode;

		if ( shadowColorNode === null ) {

			const customShadowNode = this.light.shadow.shadowNode;

			let shadowNode;

			if ( customShadowNode !== undefined ) {

				shadowNode = nodeObject( customShadowNode );

			} else {

				shadowNode = this.setupShadowNode();

			}

			this.shadowNode = shadowNode;

			this.shadowColorNode = shadowColorNode = this.colorNode.mul( shadowNode );

			this.baseColorNode = this.colorNode;

		}

		//

		if ( builder.context.getShadow ) {

			shadowColorNode = builder.context.getShadow( this, builder );

		}

		this.colorNode = shadowColorNode;

	}

	/**
	 * Unlike most other nodes, lighting nodes do not return a output node in {@link Node#setup}.
	 * The main purpose of lighting nodes is to configure the current {@link LightingModel} and/or
	 * invocate the respective interface methods.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 */
	setup( builder ) {

		this.colorNode = this.baseColorNode || this.colorNode;

		if ( this.light.castShadow ) {

			if ( builder.object.receiveShadow ) {

				this.setupShadow( builder );

			}

		} else if ( this.shadowNode !== null ) {

			this.disposeShadow();

		}

		const directLightData = this.setupDirect( builder );
		const directRectAreaLightData = this.setupDirectRectArea( builder );

		if ( directLightData ) {

			builder.lightsNode.setupDirectLight( builder, this, directLightData );

		}

		if ( directRectAreaLightData ) {

			builder.lightsNode.setupDirectRectAreaLight( builder, this, directRectAreaLightData );

		}

	}

	/**
	 * The update method is used to update light uniforms per frame.
	 * Potentially overwritten in concrete light nodes to update light
	 * specific uniforms.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	update( /*frame*/ ) {

		const { light } = this;

		this.color.copy( light.color ).multiplyScalar( light.intensity );

	}

}

/**
 * Calculates, at a given distance from a light, the falloff of the light's brightness.
 *
 * @method
 * @param {Object} inputs - The input parameter object.
 * @param {Node<float>} inputs.lightDistance - The distance of the light's position to the current fragment position.
 * @param {Node<float>} inputs.cutoffDistance - The light's cutoff distance.
 * @param {Node<float>} inputs.decayExponent - The light's decay exponent.
 * @return {Node<float>} The distance falloff.
 */
const getDistanceAttenuation = /*@__PURE__*/ Fn( ( { lightDistance, cutoffDistance, decayExponent } ) => {

	// based upon Frostbite 3 Moving to Physically-based Rendering
	// page 32, equation 26: E[window1]
	// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
	const distanceFalloff = lightDistance.pow( decayExponent ).max( 0.01 ).reciprocal();

	return cutoffDistance.greaterThan( 0 ).select(
		distanceFalloff.mul( lightDistance.div( cutoffDistance ).pow4().oneMinus().clamp().pow2() ),
		distanceFalloff
	);

}, { lightDistance: 'float', cutoffDistance: 'float', decayExponent: 'float', return: 'float' } ); // validated

const directPointLight = ( { color, lightVector, cutoffDistance, decayExponent } ) => {

	const lightDirection = lightVector.normalize();
	const lightDistance = lightVector.length();

	const attenuation = getDistanceAttenuation( {
		lightDistance,
		cutoffDistance,
		decayExponent
	} );

	const lightColor = color.mul( attenuation );

	return { lightDirection, lightColor };

};

/**
 * Module for representing point lights as nodes.
 *
 * @augments AnalyticLightNode
 */
class PointLightNode extends AnalyticLightNode {

	static get type() {

		return 'PointLightNode';

	}

	/**
	 * Constructs a new point light node.
	 *
	 * @param {?PointLight} [light=null] - The point light source.
	 */
	constructor( light = null ) {

		super( light );

		/**
		 * Uniform node representing the cutoff distance.
		 *
		 * @type {UniformNode<float>}
		 */
		this.cutoffDistanceNode = uniform( 0 ).setGroup( renderGroup );

		/**
		 * Uniform node representing the decay exponent.
		 *
		 * @type {UniformNode<float>}
		 */
		this.decayExponentNode = uniform( 2 ).setGroup( renderGroup );

	}

	/**
	 * Overwritten to updated point light specific uniforms.
	 *
	 * @param {NodeFrame} frame - A reference to the current node frame.
	 */
	update( frame ) {

		const { light } = this;

		super.update( frame );

		this.cutoffDistanceNode.value = light.distance;
		this.decayExponentNode.value = light.decay;

	}

	/**
	 * Overwritten to setup point light specific shadow.
	 *
	 * @return {PointShadowNode}
	 */
	setupShadowNode() {

		return pointShadow( this.light );

	}

	setupDirect( builder ) {

		return directPointLight( {
			color: this.colorNode,
			lightVector: this.getLightVector( builder ),
			cutoffDistance: this.cutoffDistanceNode,
			decayExponent: this.decayExponentNode
		} );

	}

}

const GOLDEN_ANGLE = 2.399963229728653;

/**
 * Returns the mip level of a PMREM that has been prefiltered for the given roughness.
 * Uses the inverse of `PMREMGenerator.lodToRoughness()`.
 *
 * @tsl
 * @function
 * @param {Node<float>} roughness - The roughness.
 * @param {Node<float>} maxLod - The last mip level of the PMREM.
 * @return {Node<float>} The mip level.
 * @see {@link https://github.com/google/filament/blob/main/shaders/src/surface_light_indirect.fs | Filament: perceptualRoughnessToLod()}
 */
const roughnessToMip = ( roughness, maxLod ) => {

	roughness = float( roughness ).clamp();

	return float( maxLod ).mul( roughness ).mul( float( 2.0 ).sub( roughness ) );

};

// Gaussian blur using stratified inverse-CDF samples on a golden-angle spiral.
const sphericalGaussianBlur = /*@__PURE__*/ Fn( ( { SAMPLES, sigma, direction, envMap } ) => {

	const outputDirection = vec3( direction ).toVar();

	const up = select( abs( outputDirection.z ).lessThan( 0.999 ), vec3( 0.0, 0.0, 1.0 ), vec3( 1.0, 0.0, 0.0 ) );
	const tangent = normalize( cross( up, outputDirection ) ).toVar();
	const bitangent = cross( outputDirection, tangent ).toVar();

	// Truncate the kernel at three standard deviations or at the antipode.
	const thetaMax = min( sigma.mul( 3.0 ), Math.PI );
	const truncation = exp( thetaMax.mul( thetaMax ).mul( -0.5 ).div( sigma.mul( sigma ) ) ).oneMinus().toVar();

	const color = vec3( 0.0 ).toVar();
	const accumWeight = float( 0.0 ).toVar();

	Loop( { start: int( 0 ), end: SAMPLES }, ( { i } ) => {

		// Stratified inverse-CDF sampling of the Gaussian, placed on a golden-angle spiral.
		const stratum = float( i ).add( 0.5 ).div( float( SAMPLES ) );
		const theta = sigma.mul( sqrt( log( stratum.mul( truncation ).oneMinus() ).mul( -2 ) ) ).toVar();
		const phi = float( i ).mul( GOLDEN_ANGLE ).toVar();

		const offset = tangent.mul( cos( phi ) ).add( bitangent.mul( sin( phi ) ) );
		const sampleDirection = outputDirection.mul( cos( theta ) ).add( offset.mul( sin( theta ) ) );

		// Correct the planar sample density to solid angle.
		const weight = sin( theta ).div( theta ).toVar();

		color.addAssign( envMap.sample( sampleDirection ).level( 0 ).rgb.mul( weight ) );
		accumWeight.addAssign( weight );

	} );

	return vec4( color.div( accumWeight ), 1.0 );

} );

// Van der Corput radical inverse.
const radicalInverse_VdC = /*@__PURE__*/ Fn( ( [ bits_immutable ] ) => {

	const bits = uint( bits_immutable ).toVar();
	bits.assign( bits.shiftLeft( uint( 16 ) ).bitOr( bits.shiftRight( uint( 16 ) ) ) );
	bits.assign( bits.bitAnd( uint( 0x55555555 ) ).shiftLeft( uint( 1 ) ).bitOr( bits.bitAnd( uint( 0xAAAAAAAA ) ).shiftRight( uint( 1 ) ) ) );
	bits.assign( bits.bitAnd( uint( 0x33333333 ) ).shiftLeft( uint( 2 ) ).bitOr( bits.bitAnd( uint( 0xCCCCCCCC ) ).shiftRight( uint( 2 ) ) ) );
	bits.assign( bits.bitAnd( uint( 0x0F0F0F0F ) ).shiftLeft( uint( 4 ) ).bitOr( bits.bitAnd( uint( 0xF0F0F0F0 ) ).shiftRight( uint( 4 ) ) ) );
	bits.assign( bits.bitAnd( uint( 0x00FF00FF ) ).shiftLeft( uint( 8 ) ).bitOr( bits.bitAnd( uint( 0xFF00FF00 ) ).shiftRight( uint( 8 ) ) ) );
	return float( bits ).mul( 2.3283064365386963e-10 ); // / 0x100000000

} );

// Hammersley sequence.
const hammersley = /*@__PURE__*/ Fn( ( [ i, N ] ) => {

	return vec2( float( i ).div( float( N ) ), radicalInverse_VdC( i ) );

} );

// GGX convolution using VNDF importance sampling. Each sample reads the mip level of the
// source cube map that matches its solid angle (filtered importance sampling), which keeps
// the estimate smooth even for tiny, very bright light sources.
const ggxConvolution = /*@__PURE__*/ Fn( ( { roughness, lodBias, envMap, direction, GGX_SAMPLES } ) => {

	const N = vec3( direction ).toVar();

	const prefilteredColor = vec3( 0.0 ).toVar();

	// For very low roughness, just sample the environment directly
	If( roughness.lessThan( 0.001 ), () => {

		prefilteredColor.assign( envMap.sample( N ).level( 0 ).rgb );

	} ).Else( () => {

		const alpha = roughness.mul( roughness ).toConst();
		const alpha2 = alpha.mul( alpha ).toConst();

		// Tangent space basis for VNDF sampling
		const up = select( abs( N.z ).lessThan( 0.999 ), vec3( 0.0, 0.0, 1.0 ), vec3( 1.0, 0.0, 0.0 ) );
		const tangent = normalize( cross( up, N ) ).toVar();
		const bitangent = cross( N, tangent ).toVar();

		const totalWeight = float( 0.0 ).toVar();

		Loop( { start: uint( 0 ), end: GGX_SAMPLES }, ( { i } ) => {

			const Xi = hammersley( i, GGX_SAMPLES );

			// With V = N, sample the reflected direction directly.
			const invQ = float( 1.0 ).div( Xi.x.oneMinus().add( alpha2.mul( Xi.x ) ) ).toConst();
			const NdotL = Xi.x.oneMinus().sub( alpha2.mul( Xi.x ) ).mul( invQ ).toConst();

			If( NdotL.greaterThan( 0.0 ), () => {

				const phi = Xi.y.mul( 2.0 * Math.PI ).toConst();
				const sinTheta = alpha.mul( 2.0 ).mul( sqrt( Xi.x.mul( Xi.x.oneMinus() ) ) ).mul( invQ ).toConst();
				const L = N.mul( NdotL ).add( tangent.mul( cos( phi ) ).add( bitangent.mul( sin( phi ) ) ).mul( sinTheta ) ).toConst();

				// Match the source mip to the sample's solid angle; see lodBias.
				const d = alpha2.mul( invQ );
				const lod = max( log2( d ).add( lodBias ), 0.0 );

				// Weight by NdotL for the split-sum approximation
				prefilteredColor.addAssign( envMap.sample( L ).level( lod ).rgb.mul( NdotL ) );
				totalWeight.addAssign( NdotL );

			} );

		} );

		prefilteredColor.divAssign( totalWeight );

	} );

	return vec4( prefilteredColor, 1.0 );

} );

// GGX convolution that weights every texel of a small source mip. Noise free and,
// for the wide lobes of the rough mip levels, cheaper than importance sampling.
const ggxIntegration = /*@__PURE__*/ Fn( ( { roughness, sourceLod, sourceSize, envMap, direction } ) => {

	const N = vec3( direction ).toVar();

	const alpha = roughness.mul( roughness ).toConst();
	const alpha2 = alpha.mul( alpha ).toConst();

	const texelSize = float( 2.0 ).div( float( sourceSize ) ).toConst();

	const prefilteredColor = vec3( 0.0 ).toVar();
	const totalWeight = float( 0.0 ).toVar();

	// Pair opposite texels: only the one in N's hemisphere contributes.
	Loop( { start: int( 0 ), end: int( 3 ), name: 'face' }, ( { face } ) => {

		Loop( { start: int( 0 ), end: sourceSize, name: 'y' }, ( { y } ) => {

			Loop( { start: int( 0 ), end: sourceSize, name: 'x' }, ( { x } ) => {

				const uv = vec2( x, y ).add( 0.5 ).mul( texelSize ).sub( 1.0 ).toConst();
				const texelDirection = select( face.equal( 0 ), vec3( 1.0, uv ), select( face.equal( 1 ), vec3( uv.x, 1.0, uv.y ), vec3( uv, 1.0 ) ) ).toVar();

				const invDistance = inverseSqrt( dot( uv, uv ).add( 1.0 ) ).toConst();
				const NdotL = dot( N, texelDirection ).toVar();
				texelDirection.mulAssign( select( NdotL.lessThan( 0.0 ), -1, 1.0 ) );
				NdotL.assign( abs( NdotL ).mul( invDistance ) );

				// With V = N, NdotH squared is ( 1 + NdotL ) / 2. Common factors
				// in the GGX distribution and texel solid angle cancel when normalized.
				const d = alpha2.add( 1.0 ).add( alpha2.sub( 1.0 ).mul( NdotL ) );
				const weight = NdotL.mul( invDistance ).mul( invDistance ).mul( invDistance ).div( d.mul( d ) ).toConst();

				prefilteredColor.addAssign( envMap.sample( texelDirection ).level( sourceLod ).rgb.mul( weight ) );
				totalWeight.addAssign( weight );

			} );

		} );

	} );

	return vec4( prefilteredColor.div( totalWeight ), 1.0 );

} );

/**
 * This class represents a cube render target. It is a special version
 * of `WebGLCubeRenderTarget` which is compatible with `WebGPURenderer`.
 *
 * @augments RenderTarget
 */
class CubeRenderTarget extends RenderTarget {

	/**
	 * Constructs a new cube render target.
	 *
	 * @param {number} [size=1] - The size of the render target.
	 * @param {RenderTarget~Options} [options] - The configuration object.
	 */
	constructor( size = 1, options = {} ) {

		super( size, size, options );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isCubeRenderTarget = true;

		const image = { width: size, height: size, depth: 1 };
		const images = [ image, image, image, image, image, image ];

		/**
		 * Overwritten with a different texture type.
		 *
		 * @type {DataArrayTexture}
		 */
		this.texture = new CubeTexture( images );
		this._setTextureOptions( options );

		// By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js)
		// in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words,
		// in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly.

		// three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped
		// and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture
		// as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures).

		this.texture.isRenderTargetTexture = true;

	}

	/**
	 * Converts the given equirectangular texture to a cube map.
	 *
	 * @param {Renderer} renderer - The renderer.
	 * @param {Texture} texture - The equirectangular texture.
	 * @return {CubeRenderTarget} A reference to this cube render target.
	 */
	fromEquirectangularTexture( renderer, texture$1 ) {

		const currentMinFilter = texture$1.minFilter;
		const currentGenerateMipmaps = texture$1.generateMipmaps;

		texture$1.generateMipmaps = true;

		this.texture.type = texture$1.type;
		this.texture.colorSpace = texture$1.colorSpace;

		this.texture.generateMipmaps = texture$1.generateMipmaps;
		this.texture.minFilter = texture$1.minFilter;
		this.texture.magFilter = texture$1.magFilter;

		const geometry = new BoxGeometry( 5, 5, 5 );

		const uvNode = equirectUV( positionWorldDirection );

		const material = new NodeMaterial();
		material.colorNode = texture( texture$1, uvNode, 0 );
		material.side = BackSide;
		material.blending = NoBlending;

		const mesh = new Mesh( geometry, material );

		const scene = new Scene();
		scene.add( mesh );

		// Avoid blurred poles
		if ( texture$1.minFilter === LinearMipmapLinearFilter ) texture$1.minFilter = LinearFilter;

		const camera = new CubeCamera( 1, 10, this );

		const currentMRT = renderer.getMRT();
		renderer.setMRT( null );

		camera.update( renderer, scene );

		renderer.setMRT( currentMRT );

		texture$1.minFilter = currentMinFilter;
		texture$1.generateMipmaps = currentGenerateMipmaps;

		mesh.geometry.dispose();
		mesh.material.dispose();

		return this;

	}

	/**
	 * Clears this cube render target.
	 *
	 * @param {Renderer} renderer - The renderer.
	 * @param {boolean} [color=true] - Whether the color buffer should be cleared or not.
	 * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not.
	 * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not.
	 */
	clear( renderer, color = true, depth = true, stencil = true ) {

		const currentRenderTarget = renderer.getRenderTarget();

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

			renderer.setRenderTarget( this, i );

			renderer.clear( color, depth, stencil );

		}

		renderer.setRenderTarget( currentRenderTarget );

	}

}

// Smaller inputs are upsampled so that every PMREM has enough mip levels.
const MIN_SIZE = 256;

// Log2 of the face size of the roughest mip level. Smaller faces can't
// represent the diffuse irradiance that is stored in this level.
const LOD_MIN = 3;

// Spiral samples per pass of the initial fromScene() blur.
const BLUR_SAMPLES = 20;

// GGX VNDF samples for the sharp mip levels.
const GGX_SAMPLES = 256;

// Integrate a small source mip for the rough levels to avoid sampling noise.
const INTEGRATION_SIZE = 16;
const INTEGRATION_LEVELS = 3;

const _origin = /*@__PURE__*/ new Vector3();
const _clearColor = /*@__PURE__*/ new Color();

// maps materials to their uniforms dictionary

const _uniformsMap = new WeakMap();

/**
 * Generates a Prefiltered, Mipmapped Radiance Environment Map (PMREM) for
 * image-based lighting. The result is a cube render target whose mip levels
 * store GGX-filtered radiance at increasing roughness, from a mirror at level 0
 * to roughness 1. See {@link PMREMGenerator.lodToRoughness} for the mapping.
 *
 * Filtering assumes the view direction equals the surface normal. Sharp levels
 * use GGX visible normal sampling (Heitz, 2018), while rough levels integrate a
 * lower-resolution source cubemap.
 *
 * @see {@link https://jcgt.org/published/0007/04/01/ | Sampling the GGX Distribution of Visible Normals}
 */
class PMREMGenerator {

	/**
	 * Constructs a new PMREM generator.
	 *
	 * @param {Renderer} renderer - The renderer.
	 */
	constructor( renderer ) {

		this._renderer = renderer;

		this._cubeSize = 0;
		this._sourceTarget = null;

		this._cubeCamera = new CubeCamera( 1, 10, null );
		this._boxMesh = new Mesh( new BoxGeometry( 5, 5, 5 ), null );

		this._cubemapMaterial = null;
		this._equirectMaterial = null;

		this._blurMaterial = null;
		this._ggxMaterial = null;
		this._integrationMaterial = null;

	}

	/**
	 * Generates a PMREM from a scene, optionally applying a Gaussian blur before
	 * GGX filtering.
	 *
	 * @param {Scene} scene - The scene to be captured.
	 * @param {number} [sigma=0] - The blur radius in radians.
	 * @param {number} [near=0.1] - The near plane distance.
	 * @param {number} [far=100] - The far plane distance.
	 * @param {Object} [options={}] - The configuration options.
	 * @param {number} [options.size=256] - The texture size of the PMREM, rounded down to a power of two and at least 256.
	 * @param {Vector3} [options.position=origin] - The position of the internal cube camera that renders the scene.
	 * @param {?CubeRenderTarget} [options.renderTarget=null] - The render target to use.
	 * @return {CubeRenderTarget} The resulting PMREM.
	 */
	fromScene( scene, sigma = 0, near = 0.1, far = 100, options = {} ) {

		const {
			size = 256,
			position = _origin,
			renderTarget = null,
		} = options;

		const renderer = this._renderer;

		this._setSize( size );

		if ( renderer.hasInitialized() === false ) {

			throw new Error( 'THREE.PMREMGenerator: .fromScene() called before the backend is initialized. Use "await renderer.init();" before using this method.' );

		}

		const pmremTarget = renderTarget || this._allocateTarget();
		const sourceTarget = this._getSourceTarget( true );

		if ( sigma > 0 ) {

			sourceTarget.texture.mipmapsAutoUpdate = false;

		}

		// Clear every captured face, independent of the application's clear settings.

		const autoClear = renderer.autoClear;
		const autoClearColor = renderer.autoClearColor;
		const autoClearDepth = renderer.autoClearDepth;
		const autoClearStencil = renderer.autoClearStencil;
		const background = scene.background;

		renderer.autoClear = true;
		renderer.autoClearColor = true;
		renderer.autoClearDepth = true;
		renderer.autoClearStencil = true;

		if ( background === null ) scene.background = renderer.getClearColor( _clearColor );

		const cubeCamera = new CubeCamera( near, far, sourceTarget );
		cubeCamera.position.copy( position );
		cubeCamera.update( renderer, scene );

		renderer.autoClear = autoClear;
		renderer.autoClearColor = autoClearColor;
		renderer.autoClearDepth = autoClearDepth;
		renderer.autoClearStencil = autoClearStencil;

		scene.background = background;

		if ( sigma > 0 ) {

			sourceTarget.texture.mipmapsAutoUpdate = true;
			this._blur( pmremTarget, sigma );

		}

		this._applyPMREM( pmremTarget );

		return pmremTarget;

	}

	/**
	 * Asynchronous version of {@link PMREMGenerator#fromScene}.
	 *
	 * @deprecated
	 * @param {Scene} scene - The scene to be captured.
	 * @param {number} [sigma=0] - The blur radius in radians.
	 * @param {number} [near=0.1] - The near plane distance.
	 * @param {number} [far=100] - The far plane distance.
	 * @param {Object} [options={}] - The configuration options.
	 * @param {number} [options.size=256] - The texture size of the PMREM, rounded down to a power of two and at least 256.
	 * @param {Vector3} [options.position=origin] - The position of the internal cube camera that renders the scene.
	 * @param {?CubeRenderTarget} [options.renderTarget=null] - The render target to use.
	 * @return {Promise<CubeRenderTarget>} A Promise that resolves with the PMREM.
	 * @see {@link PMREMGenerator#fromScene}
	 */
	async fromSceneAsync( scene, sigma = 0, near = 0.1, far = 100, options = {} ) {

		warnOnce( 'PMREMGenerator: ".fromSceneAsync()" is deprecated. Use "await renderer.init()" instead.' ); // @deprecated r181

		await this._renderer.init();

		return this.fromScene( scene, sigma, near, far, options );

	}

	/**
	 * Generates a PMREM from an LDR or HDR equirectangular texture. The cube face
	 * size is one quarter of the image width, rounded down to a power of two and
	 * at least 256.
	 *
	 * @param {Texture} equirectangular - The equirectangular texture to be converted.
	 * @param {?CubeRenderTarget} [renderTarget=null] - The render target to use.
	 * @return {CubeRenderTarget} The resulting PMREM.
	 * @see {@link PMREMGenerator#fromEquirectangularAsync}
	 */
	fromEquirectangular( equirectangular, renderTarget = null ) {

		if ( this._renderer.hasInitialized() === false ) {

			throw new Error( 'THREE.PMREMGenerator: .fromEquirectangular() called before the backend is initialized. Use "await renderer.init();" before using this method.' );

		}

		return this._fromTexture( equirectangular, renderTarget );

	}

	/**
	 * Asynchronous version of {@link PMREMGenerator#fromEquirectangular}.
	 *
	 * @deprecated
	 * @param {Texture} equirectangular - The equirectangular texture to be converted.
	 * @param {?CubeRenderTarget} [renderTarget=null] - The render target to use.
	 * @return {Promise<CubeRenderTarget>} The resulting PMREM.
	 * @see {@link PMREMGenerator#fromEquirectangular}
	 */
	async fromEquirectangularAsync( equirectangular, renderTarget = null ) {

		warnOnce( 'PMREMGenerator: ".fromEquirectangularAsync()" is deprecated. Use "await renderer.init()" instead.' ); // @deprecated r181

		await this._renderer.init();

		return this._fromTexture( equirectangular, renderTarget );

	}

	/**
	 * Generates a PMREM from an LDR or HDR cubemap. The cube face size matches
	 * the input, rounded down to a power of two and at least 256.
	 *
	 * @param {Texture} cubemap - The cubemap texture to be converted.
	 * @param {?CubeRenderTarget} [renderTarget=null] - The render target to use.
	 * @return {CubeRenderTarget} The resulting PMREM.
	 * @see {@link PMREMGenerator#fromCubemapAsync}
	 */
	fromCubemap( cubemap, renderTarget = null ) {

		if ( this._renderer.hasInitialized() === false ) {

			throw new Error( 'THREE.PMREMGenerator: .fromCubemap() called before the backend is initialized. Use "await renderer.init();" before using this method.' );

		}

		return this._fromTexture( cubemap, renderTarget );

	}

	/**
	 * Asynchronous version of {@link PMREMGenerator#fromCubemap}.
	 *
	 * @deprecated
	 * @param {Texture} cubemap - The cubemap texture to be converted.
	 * @param {?CubeRenderTarget} [renderTarget=null] - The render target to use.
	 * @return {Promise<CubeRenderTarget>} The resulting PMREM.
	 * @see {@link PMREMGenerator#fromCubemap}
	 */
	async fromCubemapAsync( cubemap, renderTarget = null ) {

		warnOnce( 'PMREMGenerator: ".fromCubemapAsync()" is deprecated. Use "await renderer.init()" instead.' ); // @deprecated r181

		await this._renderer.init();

		return this._fromTexture( cubemap, renderTarget );

	}

	/**
	 * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during
	 * your texture's network fetch for increased concurrency.
	 *
	 * @returns {Promise}
	 */
	async compileCubemapShader() {

		if ( this._cubemapMaterial === null ) {

			this._cubemapMaterial = _getCubemapMaterial();
			await this._compileMaterial( this._cubemapMaterial );

		}

	}

	/**
	 * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during
	 * your texture's network fetch for increased concurrency.
	 *
	 * @returns {Promise}
	 */
	async compileEquirectangularShader() {

		if ( this._equirectMaterial === null ) {

			this._equirectMaterial = _getEquirectMaterial();
			await this._compileMaterial( this._equirectMaterial );

		}

	}

	/**
	 * Disposes of the PMREMGenerator's internal memory. The PMREMs it returned
	 * belong to the caller and are not disposed.
	 */
	dispose() {

		if ( this._sourceTarget !== null ) this._sourceTarget.dispose();

		if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose();
		if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose();
		if ( this._blurMaterial !== null ) this._blurMaterial.dispose();
		if ( this._ggxMaterial !== null ) this._ggxMaterial.dispose();
		if ( this._integrationMaterial !== null ) this._integrationMaterial.dispose();

		this._boxMesh.geometry.dispose();

	}

	/**
	 * Returns the roughness a mip level of a PMREM has been prefiltered for.
	 * The inverse mapping is used by the renderers when sampling a PMREM.
	 *
	 * @param {number} lod - The mip level.
	 * @param {number} maxLod - The last mip level of the PMREM.
	 * @return {number} The roughness.
	 */
	static lodToRoughness( lod, maxLod ) {

		return maxLod > 0 ? 1 - Math.sqrt( 1 - lod / maxLod ) : 0;

	}

	// private interface

	_setSize( cubeSize ) {

		this._cubeSize = Math.max( MIN_SIZE, floorPowerOfTwo( cubeSize ) );

	}

	_fromTexture( texture, renderTarget ) {

		if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) {

			this._setSize( texture.image.length === 0 ? MIN_SIZE : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) );

		} else { // Equirectangular

			this._setSize( texture.image.width / 4 );

		}

		const pmremTarget = renderTarget || this._allocateTarget();

		this._textureToCubemap( texture );
		this._applyPMREM( pmremTarget );

		return pmremTarget;

	}

	_allocateTarget() {

		const size = this._cubeSize;

		const pmremTarget = _createRenderTarget( size, false, false );

		// Allocate only the prefiltered mip levels without automatic mipmap generation.

		const maxLod = Math.log2( size ) - LOD_MIN;

		for ( let lod = 0; lod <= maxLod; lod ++ ) {

			pmremTarget.texture.mipmaps.push( { width: size >> lod, height: size >> lod } );

		}

		pmremTarget.texture.name = 'PMREM';
		pmremTarget.texture.isPMREMTexture = true;

		return pmremTarget;

	}

	_getSourceTarget( depthBuffer = false ) {

		const size = this._cubeSize;
		const sourceTarget = this._sourceTarget;

		if ( sourceTarget === null || sourceTarget.width !== size || ( depthBuffer && sourceTarget.depthBuffer === false ) ) {

			if ( sourceTarget !== null ) sourceTarget.dispose();

			this._sourceTarget = _createRenderTarget( size, true, depthBuffer );

		}

		return this._sourceTarget;

	}

	async _compileMaterial( material ) {

		this._boxMesh.material = material;
		await this._renderer.compile( this._boxMesh, this._cubeCamera.children[ 0 ] );

	}

	/**
	 * Renders the box mesh with the given material into all six faces of a cube render target.
	 *
	 * @private
	 * @param {CubeRenderTarget} target - The render target.
	 * @param {number} lod - The mip level to render into.
	 * @param {NodeMaterial} material - The material.
	 */
	_renderCube( target, lod, material ) {

		const boxMesh = this._boxMesh;
		const cubeCamera = this._cubeCamera;

		boxMesh.material = material;

		cubeCamera.renderTarget = target;
		cubeCamera.activeMipmapLevel = lod;
		cubeCamera.update( this._renderer, boxMesh );

	}

	_textureToCubemap( texture ) {

		let material;

		if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) {

			if ( this._cubemapMaterial === null ) {

				this._cubemapMaterial = _getCubemapMaterial();

			}

			material = this._cubemapMaterial;

		} else {

			if ( this._equirectMaterial === null ) {

				this._equirectMaterial = _getEquirectMaterial();

			}

			material = this._equirectMaterial;

		}

		_uniformsMap.get( material ).envMap.value = texture;

		this._renderCube( this._getSourceTarget(), 0, material );

	}

	/**
	 * Prefilters the source cubemap into the mip levels of the PMREM. The sharp levels
	 * integrate the GGX lobe of their roughness with VNDF importance sampling, reading the
	 * mip level of the source that matches the solid angle of each sample. The rough
	 * levels weight every texel of a small source mip with the GGX lobe instead.
	 *
	 * @private
	 * @param {CubeRenderTarget} pmremTarget - The PMREM.
	 */
	_applyPMREM( pmremTarget ) {

		if ( this._ggxMaterial === null ) {

			this._ggxMaterial = _getGGXMaterial();
			this._integrationMaterial = _getIntegrationMaterial();

		}

		const size = this._cubeSize;
		const maxLod = pmremTarget.texture.mipmaps.length - 1;

		const ggxUniforms = _uniformsMap.get( this._ggxMaterial );
		ggxUniforms.envMap.value = this._sourceTarget.texture;

		const integrationUniforms = _uniformsMap.get( this._integrationMaterial );
		integrationUniforms.envMap.value = this._sourceTarget.texture;
		integrationUniforms.sourceLod.value = Math.log2( size / INTEGRATION_SIZE );

		for ( let lod = 0; lod <= maxLod; lod ++ ) {

			const roughness = PMREMGenerator.lodToRoughness( lod, maxLod );

			if ( lod > maxLod - INTEGRATION_LEVELS ) {

				integrationUniforms.roughness.value = roughness;

				this._renderCube( pmremTarget, lod, this._integrationMaterial );

			} else {

				// For V = N, pdf( L ) = D( H ) / 4. Match the source texel solid angle to
				// 1 / ( GGX_SAMPLES * pdf ), with a half-mip bias to reduce sampling noise.
				// The shader supplies log2 of the GGX denominator.
				const lodBias = roughness > 0 ? Math.log2( size ) + 0.5 * Math.log2( 6 / ( GGX_SAMPLES * Math.pow( roughness, 4 ) ) ) + 0.5 : 0;

				ggxUniforms.roughness.value = roughness;
				ggxUniforms.lodBias.value = lodBias;

				this._renderCube( pmremTarget, lod, this._ggxMaterial );

			}

		}

	}

	/**
	 * Applies the initial fromScene() blur in two passes using a golden-angle
	 * spiral kernel. Level 0 of the PMREM serves as the intermediate target.
	 *
	 * @private
	 * @param {CubeRenderTarget} pmremTarget - The PMREM.
	 * @param {number} sigma - The blur radius in radians.
	 */
	_blur( pmremTarget, sigma ) {

		if ( this._blurMaterial === null ) {

			this._blurMaterial = _getBlurMaterial();

		}

		const material = this._blurMaterial;
		const uniforms = _uniformsMap.get( material );

		const sourceTarget = this._sourceTarget;

		// Split the blur variance between two passes. Clamp sigma to the sphere's
		// maximum angular distance.
		uniforms.sigma.value = Math.min( sigma, Math.PI ) / Math.SQRT2;

		uniforms.envMap.value = sourceTarget.texture;
		this._renderCube( pmremTarget, 0, material );

		uniforms.envMap.value = pmremTarget.texture;
		this._renderCube( sourceTarget, 0, material );

	}

}

function _createRenderTarget( size, generateMipmaps, depthBuffer ) {

	return new CubeRenderTarget( size, {
		minFilter: LinearMipmapLinearFilter,
		generateMipmaps: generateMipmaps,
		type: HalfFloatType,
		colorSpace: LinearSRGBColorSpace,
		depthBuffer: depthBuffer
	} );

}

function _getMaterial( type, uniforms, fragmentNode ) {

	const material = new NodeMaterial();
	material.name = `PMREM_${ type }`;
	material.fragmentNode = fragmentNode;
	material.side = BackSide;
	material.blending = NoBlending;
	material.depthTest = false;
	material.depthWrite = false;

	_uniformsMap.set( material, uniforms );

	return material;

}

function _getBlurMaterial() {

	const uniforms = {
		envMap: cubeTexture(),
		sigma: uniform( 0 )
	};

	return _getMaterial( 'blur', uniforms, sphericalGaussianBlur( {
		...uniforms,
		direction: positionWorldDirection,
		SAMPLES: int( BLUR_SAMPLES )
	} ) );

}

function _getGGXMaterial() {

	const uniforms = {
		envMap: cubeTexture(),
		roughness: uniform( 0 ),
		lodBias: uniform( 0 )
	};

	return _getMaterial( 'ggx', uniforms, ggxConvolution( {
		...uniforms,
		direction: positionWorldDirection,
		GGX_SAMPLES: uint( GGX_SAMPLES )
	} ) );

}

function _getIntegrationMaterial() {

	const uniforms = {
		envMap: cubeTexture(),
		roughness: uniform( 0 ),
		sourceLod: uniform( 0 ),
		sourceSize: uniform( INTEGRATION_SIZE, 'int' ) // a uniform so the loops aren't unrolled
	};

	return _getMaterial( 'integration', uniforms, ggxIntegration( {
		...uniforms,
		direction: positionWorldDirection
	} ) );

}

function _getCubemapMaterial() {

	const uniforms = {
		envMap: cubeTexture()
	};

	return _getMaterial( 'cubemap', uniforms, uniforms.envMap.sample( positionWorldDirection ) );

}

function _getEquirectMaterial() {

	const uniforms = {
		envMap: texture()
	};

	const fragmentNode = Fn( () => {

		// Average four subpixel samples to preserve small, bright features.
		const direction = positionWorldDirection;
		const dx = direction.dFdx().mul( 0.25 ).toConst();
		const dy = direction.dFdy().mul( 0.25 ).toConst();

		const color = uniforms.envMap.sample( equirectUV( direction.sub( dx ).sub( dy ).normalize() ) ).level( 0 ).rgb
			.add( uniforms.envMap.sample( equirectUV( direction.add( dx ).sub( dy ).normalize() ) ).level( 0 ).rgb )
			.add( uniforms.envMap.sample( equirectUV( direction.sub( dx ).add( dy ).normalize() ) ).level( 0 ).rgb )
			.add( uniforms.envMap.sample( equirectUV( direction.add( dx ).add( dy ).normalize() ) ).level( 0 ).rgb );

		return vec4( color.mul( 0.25 ), 1.0 );

	} )();

	return _getMaterial( 'equirect', uniforms, fragmentNode );

}

const _cache = new WeakMap();

/**
 * Generates a PMREM from the given texture.
 *
 * @private
 * @param {Texture} texture - The texture to create the PMREM for.
 * @param {Renderer} renderer - The renderer.
 * @param {PMREMGenerator} generator - The PMREM generator.
 * @return {?Texture} The PMREM.
 */
function _getPMREMFromTexture( texture, renderer, generator ) {

	const cache = _getCache( renderer );

	let renderTarget = cache.get( texture );

	const pmremVersion = renderTarget !== undefined ? renderTarget.texture.pmremVersion : -1;

	if ( pmremVersion !== texture.pmremVersion ) {

		const image = texture.image;

		if ( texture.isCubeTexture ) {

			if ( isCubeMapReady( image ) ) {

				renderTarget = generator.fromCubemap( texture, renderTarget );

			} else {

				return null;

			}


		} else {

			if ( isEquirectangularMapReady( image ) ) {

				renderTarget = generator.fromEquirectangular( texture, renderTarget );

			} else {

				return null;

			}

		}

		renderTarget.texture.pmremVersion = texture.pmremVersion;

		// add dispose event listener for new PMREMs

		if ( cache.has( texture ) === false ) {

			const onDispose = () => {

				texture.removeEventListener( 'dispose', onDispose );

				const pmrem = cache.get( texture );

				if ( pmrem !== undefined ) {

					pmrem.texture.pmremVersion = -1;
					pmrem.dispose();
					cache.delete( texture );

				}

			};

			texture.addEventListener( 'dispose', onDispose );

		}

		//

		cache.set( texture, renderTarget );

	}

	return renderTarget.texture;

}

/**
 * Returns a cache that stores generated PMREMs for the respective textures.
 * A cache must be maintained per renderer since PMREMs are render target textures
 * which can't be shared across render contexts.
 *
 * @private
 * @param {Renderer} renderer - The renderer.
 * @return {WeakMap<Texture, CubeRenderTarget>} The PMREM cache.
 */
function _getCache( renderer ) {

	let rendererCache = _cache.get( renderer );

	if ( rendererCache === undefined ) {

		rendererCache = new WeakMap();
		_cache.set( renderer, rendererCache );

	}

	return rendererCache;

}

/**
 * This node represents a PMREM which is a special type of preprocessed
 * environment map intended for PBR materials.
 *
 * ```js
 * const material = new MeshStandardNodeMaterial();
 * material.envNode = pmremTexture( envMap );
 * ```
 *
 * @augments Node
 */
class PMREMNode extends Node {

	static get type() {

		return 'PMREMNode';

	}

	/**
	 * Constructs a new PMREM node.
	 *
	 * @param {Texture} value - The input texture.
	 * @param {Node<vec3>} [uvNode=null] - The uv node.
	 * @param {Node<float>} [levelNode=null] - The level node.
	 */
	constructor( value, uvNode = null, levelNode = null ) {

		super( 'vec3' );

		/**
		 * Reference to the input texture.
		 *
		 * @private
		 * @type {Texture}
		 */
		this._value = value;

		/**
		 * Reference to the generated PMREM.
		 *
		 * @private
		 * @type {Texture | null}
		 * @default null
		 */
		this._pmrem = null;

		/**
		 *  The uv node.
		 *
		 * @type {Node<vec3>}
		 */
		this.uvNode = uvNode;

		/**
		 *  The level node.
		 *
		 * @type {Node<float>}
		 */
		this.levelNode = levelNode;

		/**
		 * Reference to a PMREM generator.
		 *
		 * @private
		 * @type {?PMREMGenerator}
		 * @default null
		 */
		this._generator = null;

		const defaultTexture = new CubeTexture();
		defaultTexture.isRenderTargetTexture = true;

		/**
		 * The texture node holding the generated PMREM.
		 *
		 * @private
		 * @type {CubeTextureNode}
		 */
		this._texture = cubeTexture( defaultTexture );

		/**
		 * A uniform representing the last mip level of the PMREM.
		 *
		 * @private
		 * @type {UniformNode<float>}
		 */
		this._maxLod = uniform( 0 );

		/**
		 * A uniform representing the width of the sharpest mip level of the PMREM.
		 *
		 * @private
		 * @type {UniformNode<float>}
		 */
		this._size = uniform( 0 );

		/**
		 * The `updateBeforeType` is set to `NodeUpdateType.RENDER`.
		 *
		 * @type {string}
		 * @default 'render'
		 */
		this.updateBeforeType = NodeUpdateType.RENDER;

	}

	set value( value ) {

		this._value = value;
		this._pmrem = null;

	}

	/**
	 * The node's texture value.
	 *
	 * @type {Texture}
	 */
	get value() {

		return this._value;

	}

	/**
	 * Uses the given PMREM texture to update internal values.
	 *
	 * @param {Texture} texture - The PMREM texture.
	 */
	updateFromTexture( texture ) {

		const mipmaps = texture.isCompressedCubeTexture ? texture.image[ 0 ].mipmaps : texture.mipmaps;

		this._texture.value = texture;
		this._maxLod.value = mipmaps.length - 1;
		this._size.value = mipmaps[ 0 ].width;

	}

	updateBefore( frame ) {

		let pmrem = this._pmrem;

		const pmremVersion = pmrem ? pmrem.pmremVersion : -1;
		const texture = this._value;

		if ( pmremVersion !== texture.pmremVersion ) {

			if ( texture.isPMREMTexture === true ) {

				pmrem = texture;

			} else {

				if ( this._generator === null ) {

					this._generator = new PMREMGenerator( frame.renderer );

				}

				pmrem = _getPMREMFromTexture( texture, frame.renderer, this._generator );

			}

			if ( pmrem !== null ) {

				this._pmrem = pmrem;

				this.updateFromTexture( pmrem );

			}

		}

	}

	setup( builder ) {

		this.updateBefore( builder );

		//

		let uvNode = this.uvNode;

		if ( uvNode === null && builder.context.getUV ) {

			uvNode = builder.context.getUV( this, builder );

		}

		//

		let levelNode = this.levelNode;

		if ( levelNode === null && builder.context.getTextureLevel ) {

			levelNode = builder.context.getTextureLevel( this );

		}

		//

		return this._texture.sample( materialEnvRotation.mul( uvNode ) ).level( roughnessToMip( levelNode, this._maxLod ) ).rgb;

	}

	dispose() {

		super.dispose();

		if ( this._generator !== null ) this._generator.dispose();

	}

}

/**
 * Returns `true` if the given cube map image has been fully loaded.
 *
 * @private
 * @param {?Array<(Image|Object)>} [image] - The cube map image.
 * @return {boolean} Whether the given cube map is ready or not.
 */
function isCubeMapReady( image ) {

	if ( image === null || image === undefined ) return false;

	let count = 0;
	const length = 6;

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

		if ( image[ i ] !== undefined ) count ++;

	}

	return count === length;


}

/**
 * Returns `true` if the given equirectangular image has been fully loaded.
 *
 * @private
 * @param {(Image|Object)} image - The equirectangular image.
 * @return {boolean} Whether the given cube map is ready or not.
 */
function isEquirectangularMapReady( image ) {

	if ( image === null || image === undefined ) return false;

	return image.height > 0;

}

/**
 * TSL function for creating a PMREM node.
 *
 * @tsl
 * @function
 * @param {Texture} value - The input texture.
 * @param {?Node<vec3>} [uvNode=null] - The uv node.
 * @param {?Node<float>} [levelNode=null] - The level node.
 * @returns {PMREMNode}
 */
const pmremTexture = /*@__PURE__*/ nodeProxy( PMREMNode ).setParameterLength( 1, 3 );

/**
 * Creates a 2x2 checkerboard pattern that can be used as procedural texture data.
 *
 * @tsl
 * @function
 * @param {Node<vec2>} coord - The uv coordinates.
 * @return {Node<float>} The result data.
 */
const checker = /*@__PURE__*/ Fn( ( [ coord = uv$1() ] ) => {

	const uv = coord.mul( 2.0 );

	const cx = uv.x.floor();
	const cy = uv.y.floor();
	const result = cx.add( cy ).mod( 2.0 );

	return result.sign();

}, { coord: 'vec2', return: 'float' } );

/**
 * Generates a circle based on the uv coordinates.
 *
 * @tsl
 * @function
 * @param {Node<vec2>} coord - The uv to generate the circle.
 * @return {Node<float>} The circle shape.
 */
const shapeCircle = /*@__PURE__*/ Fn( ( [ coord = uv$1() ], { renderer, material } ) => {

	const len2 = lengthSq( coord.mul( 2 ).sub( 1 ) );

	let alpha;

	if ( material.alphaToCoverage && renderer.currentSamples > 0 ) {

		const dlen = float( len2.fwidth() ).toVar();

		alpha = smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus();

	} else {

		alpha = select( len2.greaterThan( 1.0 ), 0, 1 );

	}

	return alpha;

} );

const mx_rotate2d = ( input, amount = 0 ) => {

	input = vec2( input );
	amount = float( amount );

	const rotationRadians = mul( amount, Math.PI / 180.0 );
	const sa = sin( rotationRadians );
	const ca = cos( rotationRadians );
	const x = input.x;
	const y = input.y;

	return vec2( add( mul( ca, x ), mul( sa, y ) ), sub( mul( ca, y ), mul( sa, x ) ) );

};

const mx_rotate3d = ( input, amount = 0, axis = vec3( 0, 1, 0 ) ) => {

	input = vec3( input );
	amount = float( amount );
	axis = vec3( axis );

	const normalizedAxis = normalize( axis );
	const rotationRadians = mul( amount, Math.PI / 180.0 );
	const s = sin( rotationRadians );
	const c = cos( rotationRadians );
	const oc = sub( 1, c );

	// based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
	// but the code in the Wikipedia article is for v' = M * v, where as
	// MaterialX is v' = v * M, thus the order of parameters into the first cross are reversed
	return input.mul( c )
		.add( input.cross( normalizedAxis ).mul( s ) )
		.add( normalizedAxis.mul( normalizedAxis.dot( input ).mul( oc ) ) );

};

const mx_select = /*@__PURE__*/ Fn( ( [ b_immutable, t_immutable, f_immutable ] ) => {

	const f = float( f_immutable ).toVar();
	const t = float( t_immutable ).toVar();
	const b = bool( b_immutable ).toVar();

	return select( b, t, f ).uniformFlow();

}, { b: 'bool', t: 'float', f: 'float', return: 'float' } );

const mx_negate_if = /*@__PURE__*/ Fn( ( [ val_immutable, b_immutable ] ) => {

	const b = bool( b_immutable ).toVar();
	const val = float( val_immutable ).toVar();

	return select( b, val.negate(), val ).uniformFlow();

}, { val: 'float', b: 'bool', return: 'float' } );

const mx_floor = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => {

	const x = float( x_immutable ).toVar();

	return int( floor( x ) );

}, { x: 'float', return: 'int' } );

const mx_floorfrac = /*@__PURE__*/ Fn( ( [ x_immutable, i ] ) => {

	const x = float( x_immutable ).toVar();
	i.assign( mx_floor( x ) );

	return x.sub( float( i ) );

} );

const mxBilerpValue = ( v0, v1, v2, v3, s, t ) => {

	const s1 = float( sub( 1.0, s ) ).toVar();
	return sub( 1.0, t ).mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) );

};

const mxTrilerpValue = ( v0, v1, v2, v3, v4, v5, v6, v7, s, t, r ) => {

	const s1 = float( sub( 1.0, s ) ).toVar();
	const t1 = float( sub( 1.0, t ) ).toVar();
	const r1 = float( sub( 1.0, r ) ).toVar();
	return r1.mul( t1.mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ) ).add( r.mul( t1.mul( v4.mul( s1 ).add( v5.mul( s ) ) ).add( t.mul( v6.mul( s1 ).add( v7.mul( s ) ) ) ) ) );

};

const mx_bilerp_0 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, s_immutable, t_immutable ] ) => {

	const t = float( t_immutable ).toVar();
	const s = float( s_immutable ).toVar();
	const v3 = float( v3_immutable ).toVar();
	const v2 = float( v2_immutable ).toVar();
	const v1 = float( v1_immutable ).toVar();
	const v0 = float( v0_immutable ).toVar();

	return mxBilerpValue( v0, v1, v2, v3, s, t );

}, {
	v0: 'float',
	v1: 'float',
	v2: 'float',
	v3: 'float',
	s: 'float',
	t: 'float',
	return: 'float'
} );

const mx_bilerp_1 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, s_immutable, t_immutable ] ) => {

	const t = float( t_immutable ).toVar();
	const s = float( s_immutable ).toVar();
	const v3 = vec3( v3_immutable ).toVar();
	const v2 = vec3( v2_immutable ).toVar();
	const v1 = vec3( v1_immutable ).toVar();
	const v0 = vec3( v0_immutable ).toVar();

	return mxBilerpValue( v0, v1, v2, v3, s, t );

}, {
	v0: 'vec3',
	v1: 'vec3',
	v2: 'vec3',
	v3: 'vec3',
	s: 'float',
	t: 'float',
	return: 'vec3'
} );

const mx_bilerp = /*@__PURE__*/ overloadingFn( [ mx_bilerp_0, mx_bilerp_1 ] );

const mx_trilerp_0 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, v4_immutable, v5_immutable, v6_immutable, v7_immutable, s_immutable, t_immutable, r_immutable ] ) => {

	const r = float( r_immutable ).toVar();
	const t = float( t_immutable ).toVar();
	const s = float( s_immutable ).toVar();
	const v7 = float( v7_immutable ).toVar();
	const v6 = float( v6_immutable ).toVar();
	const v5 = float( v5_immutable ).toVar();
	const v4 = float( v4_immutable ).toVar();
	const v3 = float( v3_immutable ).toVar();
	const v2 = float( v2_immutable ).toVar();
	const v1 = float( v1_immutable ).toVar();
	const v0 = float( v0_immutable ).toVar();

	return mxTrilerpValue( v0, v1, v2, v3, v4, v5, v6, v7, s, t, r );

}, {
	v0: 'float',
	v1: 'float',
	v2: 'float',
	v3: 'float',
	v4: 'float',
	v5: 'float',
	v6: 'float',
	v7: 'float',
	s: 'float',
	t: 'float',
	r: 'float',
	return: 'float'
} );

const mx_trilerp_1 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, v4_immutable, v5_immutable, v6_immutable, v7_immutable, s_immutable, t_immutable, r_immutable ] ) => {

	const r = float( r_immutable ).toVar();
	const t = float( t_immutable ).toVar();
	const s = float( s_immutable ).toVar();
	const v7 = vec3( v7_immutable ).toVar();
	const v6 = vec3( v6_immutable ).toVar();
	const v5 = vec3( v5_immutable ).toVar();
	const v4 = vec3( v4_immutable ).toVar();
	const v3 = vec3( v3_immutable ).toVar();
	const v2 = vec3( v2_immutable ).toVar();
	const v1 = vec3( v1_immutable ).toVar();
	const v0 = vec3( v0_immutable ).toVar();

	return mxTrilerpValue( v0, v1, v2, v3, v4, v5, v6, v7, s, t, r );

}, {
	v0: 'vec3',
	v1: 'vec3',
	v2: 'vec3',
	v3: 'vec3',
	v4: 'vec3',
	v5: 'vec3',
	v6: 'vec3',
	v7: 'vec3',
	s: 'float',
	t: 'float',
	r: 'float',
	return: 'vec3'
} );

const mx_trilerp = /*@__PURE__*/ overloadingFn( [ mx_trilerp_0, mx_trilerp_1 ] );

const mx_gradient_float_0 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable ] ) => {

	const y = float( y_immutable ).toVar();
	const x = float( x_immutable ).toVar();
	const hash = uint( hash_immutable ).toVar();
	const h = uint( hash.bitAnd( uint( 7 ) ) ).toVar();
	const u = float( mx_select( h.lessThan( uint( 4 ) ), x, y ) ).toVar();
	const v = float( mul( 2.0, mx_select( h.lessThan( uint( 4 ) ), y, x ) ) ).toVar();

	return mx_negate_if( u, bool( h.bitAnd( uint( 1 ) ) ) ).add( mx_negate_if( v, bool( h.bitAnd( uint( 2 ) ) ) ) );

}, { hash: 'uint', x: 'float', y: 'float', return: 'float' } );

const mx_gradient_float_1 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable, z_immutable ] ) => {

	const z = float( z_immutable ).toVar();
	const y = float( y_immutable ).toVar();
	const x = float( x_immutable ).toVar();
	const hash = uint( hash_immutable ).toVar();
	const h = uint( hash.bitAnd( uint( 15 ) ) ).toVar();
	const u = float( mx_select( h.lessThan( uint( 8 ) ), x, y ) ).toVar();
	const v = float( mx_select( h.lessThan( uint( 4 ) ), y, mx_select( h.equal( uint( 12 ) ).or( h.equal( uint( 14 ) ) ), x, z ) ) ).toVar();

	return mx_negate_if( u, bool( h.bitAnd( uint( 1 ) ) ) ).add( mx_negate_if( v, bool( h.bitAnd( uint( 2 ) ) ) ) );

}, {
	hash: 'uint',
	x: 'float',
	y: 'float',
	z: 'float',
	return: 'float'
} );

const mx_gradient_float = /*@__PURE__*/ overloadingFn( [ mx_gradient_float_0, mx_gradient_float_1 ] );

const mx_gradient_vec3_0 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable ] ) => {

	const y = float( y_immutable ).toVar();
	const x = float( x_immutable ).toVar();
	const hash = uvec3( hash_immutable ).toVar();

	return vec3( mx_gradient_float( hash.x, x, y ), mx_gradient_float( hash.y, x, y ), mx_gradient_float( hash.z, x, y ) );

}, { hash: 'uvec3', x: 'float', y: 'float', return: 'vec3' } );

const mx_gradient_vec3_1 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable, z_immutable ] ) => {

	const z = float( z_immutable ).toVar();
	const y = float( y_immutable ).toVar();
	const x = float( x_immutable ).toVar();
	const hash = uvec3( hash_immutable ).toVar();

	return vec3( mx_gradient_float( hash.x, x, y, z ), mx_gradient_float( hash.y, x, y, z ), mx_gradient_float( hash.z, x, y, z ) );

}, {
	hash: 'uvec3',
	x: 'float',
	y: 'float',
	z: 'float',
	return: 'vec3'
} );

const mx_gradient_vec3 = /*@__PURE__*/ overloadingFn( [ mx_gradient_vec3_0, mx_gradient_vec3_1 ] );

const mx_gradient_scale2d_0 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => {

	const v = float( v_immutable ).toVar();

	return mul( 0.6616, v );

}, { v: 'float', return: 'float' } );

const mx_gradient_scale3d_0 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => {

	const v = float( v_immutable ).toVar();

	return mul( 0.9820, v );

}, { v: 'float', return: 'float' } );

const mx_gradient_scale2d_1 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => {

	const v = vec3( v_immutable ).toVar();

	return mul( 0.6616, v );

}, { v: 'vec3', return: 'vec3' } );

const mx_gradient_scale2d = /*@__PURE__*/ overloadingFn( [ mx_gradient_scale2d_0, mx_gradient_scale2d_1 ] );

const mx_gradient_scale3d_1 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => {

	const v = vec3( v_immutable ).toVar();

	return mul( 0.9820, v );

}, { v: 'vec3', return: 'vec3' } );

const mx_gradient_scale3d = /*@__PURE__*/ overloadingFn( [ mx_gradient_scale3d_0, mx_gradient_scale3d_1 ] );

const mx_rotl32 = /*@__PURE__*/ Fn( ( [ x_immutable, k_immutable ] ) => {

	const k = int( k_immutable ).toVar();
	const x = uint( x_immutable ).toVar();

	return x.shiftLeft( k ).bitOr( x.shiftRight( int( 32 ).sub( k ) ) );

}, { x: 'uint', k: 'int', return: 'uint' } );

const mx_bjmix = /*@__PURE__*/ Fn( ( [ a, b, c ] ) => {

	a = uint( a ).toVar();
	b = uint( b ).toVar();
	c = uint( c ).toVar();
	a.subAssign( c );
	a.bitXorAssign( mx_rotl32( c, int( 4 ) ) );
	c.addAssign( b );
	b.subAssign( a );
	b.bitXorAssign( mx_rotl32( a, int( 6 ) ) );
	a.addAssign( c );
	c.subAssign( b );
	c.bitXorAssign( mx_rotl32( b, int( 8 ) ) );
	b.addAssign( a );
	a.subAssign( c );
	a.bitXorAssign( mx_rotl32( c, int( 16 ) ) );
	c.addAssign( b );
	b.subAssign( a );
	b.bitXorAssign( mx_rotl32( a, int( 19 ) ) );
	a.addAssign( c );
	c.subAssign( b );
	c.bitXorAssign( mx_rotl32( b, int( 4 ) ) );
	b.addAssign( a );

	return uvec3( a, b, c );

}, { a: 'uint', b: 'uint', c: 'uint', return: 'uvec3' } );

const mx_bjfinal = /*@__PURE__*/ Fn( ( [ a_immutable, b_immutable, c_immutable ] ) => {

	const c = uint( c_immutable ).toVar();
	const b = uint( b_immutable ).toVar();
	const a = uint( a_immutable ).toVar();
	c.bitXorAssign( b );
	c.subAssign( mx_rotl32( b, int( 14 ) ) );
	a.bitXorAssign( c );
	a.subAssign( mx_rotl32( c, int( 11 ) ) );
	b.bitXorAssign( a );
	b.subAssign( mx_rotl32( a, int( 25 ) ) );
	c.bitXorAssign( b );
	c.subAssign( mx_rotl32( b, int( 16 ) ) );
	a.bitXorAssign( c );
	a.subAssign( mx_rotl32( c, int( 4 ) ) );
	b.bitXorAssign( a );
	b.subAssign( mx_rotl32( a, int( 14 ) ) );
	c.bitXorAssign( b );
	c.subAssign( mx_rotl32( b, int( 24 ) ) );

	return c;

}, { a: 'uint', b: 'uint', c: 'uint', return: 'uint' } );

const mx_bits_to_01 = /*@__PURE__*/ Fn( ( [ bits_immutable ] ) => {

	const bits = uint( bits_immutable ).toVar();

	return float( bits ).div( float( uint( 0xffffffff ) ) );

}, { bits: 'uint', return: 'float' } );

const mx_fade = /*@__PURE__*/ Fn( ( [ t_immutable ] ) => {

	const t = float( t_immutable ).toVar();

	return t.mul( t ).mul( t ).mul( t.mul( t.mul( 6.0 ).sub( 15.0 ) ).add( 10.0 ) );

}, { t: 'float', return: 'float' } );

const mx_hash_int_0 = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => {

	const x = int( x_immutable ).toVar();
	const len = uint( uint( 1 ) ).toVar();
	const seed = uint( uint( 0xdeadbeef ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ).toVar();

	return mx_bjfinal( seed.add( uint( x ) ), seed, seed );

}, { x: 'int', return: 'uint' } );

const mx_hash_int_1 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable ] ) => {

	const y = int( y_immutable ).toVar();
	const x = int( x_immutable ).toVar();
	const len = uint( uint( 2 ) ).toVar();
	const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
	a.assign( b.assign( c.assign( uint( 0xdeadbeef ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) );
	a.addAssign( uint( x ) );
	b.addAssign( uint( y ) );

	return mx_bjfinal( a, b, c );

}, { x: 'int', y: 'int', return: 'uint' } );

const mx_hash_int_2 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable ] ) => {

	const z = int( z_immutable ).toVar();
	const y = int( y_immutable ).toVar();
	const x = int( x_immutable ).toVar();
	const len = uint( uint( 3 ) ).toVar();
	const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
	a.assign( b.assign( c.assign( uint( 0xdeadbeef ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) );
	a.addAssign( uint( x ) );
	b.addAssign( uint( y ) );
	c.addAssign( uint( z ) );

	return mx_bjfinal( a, b, c );

}, { x: 'int', y: 'int', z: 'int', return: 'uint' } );

const mx_hash_int_3 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable, xx_immutable ] ) => {

	const xx = int( xx_immutable ).toVar();
	const z = int( z_immutable ).toVar();
	const y = int( y_immutable ).toVar();
	const x = int( x_immutable ).toVar();
	const len = uint( uint( 4 ) ).toVar();
	const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
	a.assign( b.assign( c.assign( uint( 0xdeadbeef ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) );
	a.addAssign( uint( x ) );
	b.addAssign( uint( y ) );
	c.addAssign( uint( z ) );
	const mixed = uvec3( mx_bjmix( a, b, c ) ).toVar();
	a.assign( mixed.x );
	b.assign( mixed.y );
	c.assign( mixed.z );
	a.addAssign( uint( xx ) );

	return mx_bjfinal( a, b, c );

}, {
	x: 'int',
	y: 'int',
	z: 'int',
	xx: 'int',
	return: 'uint'
} );

const mx_hash_int_4 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable, xx_immutable, yy_immutable ] ) => {

	const yy = int( yy_immutable ).toVar();
	const xx = int( xx_immutable ).toVar();
	const z = int( z_immutable ).toVar();
	const y = int( y_immutable ).toVar();
	const x = int( x_immutable ).toVar();
	const len = uint( uint( 5 ) ).toVar();
	const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
	a.assign( b.assign( c.assign( uint( 0xdeadbeef ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) );
	a.addAssign( uint( x ) );
	b.addAssign( uint( y ) );
	c.addAssign( uint( z ) );
	const mixed = uvec3( mx_bjmix( a, b, c ) ).toVar();
	a.assign( mixed.x );
	b.assign( mixed.y );
	c.assign( mixed.z );
	a.addAssign( uint( xx ) );
	b.addAssign( uint( yy ) );

	return mx_bjfinal( a, b, c );

}, {
	x: 'int',
	y: 'int',
	z: 'int',
	xx: 'int',
	yy: 'int',
	return: 'uint'
} );

const mx_hash_int = /*@__PURE__*/ overloadingFn( [ mx_hash_int_0, mx_hash_int_1, mx_hash_int_2, mx_hash_int_3, mx_hash_int_4 ] );

const mx_hash_vec3_0 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable ] ) => {

	const y = int( y_immutable ).toVar();
	const x = int( x_immutable ).toVar();
	const h = uint( mx_hash_int( x, y ) ).toVar();
	const result = uvec3().toVar();
	result.x.assign( h.bitAnd( int( 0xFF ) ) );
	result.y.assign( h.shiftRight( int( 8 ) ).bitAnd( int( 0xFF ) ) );
	result.z.assign( h.shiftRight( int( 16 ) ).bitAnd( int( 0xFF ) ) );

	return result;

}, { x: 'int', y: 'int', return: 'uvec3' } );

const mx_hash_vec3_1 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable ] ) => {

	const z = int( z_immutable ).toVar();
	const y = int( y_immutable ).toVar();
	const x = int( x_immutable ).toVar();
	const h = uint( mx_hash_int( x, y, z ) ).toVar();
	const result = uvec3().toVar();
	result.x.assign( h.bitAnd( int( 0xFF ) ) );
	result.y.assign( h.shiftRight( int( 8 ) ).bitAnd( int( 0xFF ) ) );
	result.z.assign( h.shiftRight( int( 16 ) ).bitAnd( int( 0xFF ) ) );

	return result;

}, { x: 'int', y: 'int', z: 'int', return: 'uvec3' } );

const mx_hash_vec3 = /*@__PURE__*/ overloadingFn( [ mx_hash_vec3_0, mx_hash_vec3_1 ] );

const mx_perlin_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = vec2( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar();
	const fx = float( mx_floorfrac( p.x, X ) ).toVar();
	const fy = float( mx_floorfrac( p.y, Y ) ).toVar();
	const u = float( mx_fade( fx ) ).toVar();
	const v = float( mx_fade( fy ) ).toVar();
	const result = float( mx_bilerp( mx_gradient_float( mx_hash_int( X, Y ), fx, fy ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y ), fx.sub( 1.0 ), fy ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ) ), fx, fy.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ) ), u, v ) ).toVar();

	return mx_gradient_scale2d( result );

}, { p: 'vec2', return: 'float' } );

const mx_perlin_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = vec3( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
	const fx = float( mx_floorfrac( p.x, X ) ).toVar();
	const fy = float( mx_floorfrac( p.y, Y ) ).toVar();
	const fz = float( mx_floorfrac( p.z, Z ) ).toVar();
	const u = float( mx_fade( fx ) ).toVar();
	const v = float( mx_fade( fy ) ).toVar();
	const w = float( mx_fade( fz ) ).toVar();
	const result = float( mx_trilerp( mx_gradient_float( mx_hash_int( X, Y, Z ), fx, fy, fz ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y, Z ), fx.sub( 1.0 ), fy, fz ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ), Z ), fx, fy.sub( 1.0 ), fz ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz ), mx_gradient_float( mx_hash_int( X, Y, Z.add( int( 1 ) ) ), fx, fy, fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y, Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy, fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx, fy.sub( 1.0 ), fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz.sub( 1.0 ) ), u, v, w ) ).toVar();

	return mx_gradient_scale3d( result );

}, { p: 'vec3', return: 'float' } );

const mx_perlin_noise_float = /*@__PURE__*/ overloadingFn( [ mx_perlin_noise_float_0, mx_perlin_noise_float_1 ] );

const mx_perlin_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = vec2( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar();
	const fx = float( mx_floorfrac( p.x, X ) ).toVar();
	const fy = float( mx_floorfrac( p.y, Y ) ).toVar();
	const u = float( mx_fade( fx ) ).toVar();
	const v = float( mx_fade( fy ) ).toVar();
	const result = vec3( mx_bilerp( mx_gradient_vec3( mx_hash_vec3( X, Y ), fx, fy ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y ), fx.sub( 1.0 ), fy ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ) ), fx, fy.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ) ), u, v ) ).toVar();

	return mx_gradient_scale2d( result );

}, { p: 'vec2', return: 'vec3' } );

const mx_perlin_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = vec3( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
	const fx = float( mx_floorfrac( p.x, X ) ).toVar();
	const fy = float( mx_floorfrac( p.y, Y ) ).toVar();
	const fz = float( mx_floorfrac( p.z, Z ) ).toVar();
	const u = float( mx_fade( fx ) ).toVar();
	const v = float( mx_fade( fy ) ).toVar();
	const w = float( mx_fade( fz ) ).toVar();
	const result = vec3( mx_trilerp( mx_gradient_vec3( mx_hash_vec3( X, Y, Z ), fx, fy, fz ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y, Z ), fx.sub( 1.0 ), fy, fz ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ), Z ), fx, fy.sub( 1.0 ), fz ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz ), mx_gradient_vec3( mx_hash_vec3( X, Y, Z.add( int( 1 ) ) ), fx, fy, fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y, Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy, fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx, fy.sub( 1.0 ), fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz.sub( 1.0 ) ), u, v, w ) ).toVar();

	return mx_gradient_scale3d( result );

}, { p: 'vec3', return: 'vec3' } );

const mx_perlin_noise_vec3 = /*@__PURE__*/ overloadingFn( [ mx_perlin_noise_vec3_0, mx_perlin_noise_vec3_1 ] );

const mx_cell_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = float( p_immutable ).toVar();
	const ix = int( mx_floor( p ) ).toVar();

	return mx_bits_to_01( mx_hash_int( ix ) );

}, { p: 'float', return: 'float' } );

const mx_cell_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = vec2( p_immutable ).toVar();
	const ix = int( mx_floor( p.x ) ).toVar();
	const iy = int( mx_floor( p.y ) ).toVar();

	return mx_bits_to_01( mx_hash_int( ix, iy ) );

}, { p: 'vec2', return: 'float' } );

const mx_cell_noise_float_2 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = vec3( p_immutable ).toVar();
	const ix = int( mx_floor( p.x ) ).toVar();
	const iy = int( mx_floor( p.y ) ).toVar();
	const iz = int( mx_floor( p.z ) ).toVar();

	return mx_bits_to_01( mx_hash_int( ix, iy, iz ) );

}, { p: 'vec3', return: 'float' } );

const mx_cell_noise_float_3 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = vec4( p_immutable ).toVar();
	const ix = int( mx_floor( p.x ) ).toVar();
	const iy = int( mx_floor( p.y ) ).toVar();
	const iz = int( mx_floor( p.z ) ).toVar();
	const iw = int( mx_floor( p.w ) ).toVar();

	return mx_bits_to_01( mx_hash_int( ix, iy, iz, iw ) );

}, { p: 'vec4', return: 'float' } );

const mx_cell_noise_float$1 = /*@__PURE__*/ overloadingFn( [ mx_cell_noise_float_0, mx_cell_noise_float_1, mx_cell_noise_float_2, mx_cell_noise_float_3 ] );

const mx_cell_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = float( p_immutable ).toVar();
	const ix = int( mx_floor( p ) ).toVar();

	return vec3(
		mx_bits_to_01( mx_hash_int( ix, int( 0 ) ) ),
		mx_bits_to_01( mx_hash_int( ix, int( 1 ) ) ),
		mx_bits_to_01( mx_hash_int( ix, int( 2 ) ) )
	);

}, { p: 'float', return: 'vec3' } );

const mx_cell_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = vec2( p_immutable ).toVar();
	const ix = int( mx_floor( p.x ) ).toVar();
	const iy = int( mx_floor( p.y ) ).toVar();

	return vec3(
		mx_bits_to_01( mx_hash_int( ix, iy, int( 0 ) ) ),
		mx_bits_to_01( mx_hash_int( ix, iy, int( 1 ) ) ),
		mx_bits_to_01( mx_hash_int( ix, iy, int( 2 ) ) )
	);

}, { p: 'vec2', return: 'vec3' } );

const mx_cell_noise_vec3_2 = /*@__PURE__*/ Fn( ( [ positionInput ] ) => {

	const position = vec3( positionInput ).toVar();
	const ix = int( floor( position.x ) ).toVar();
	const iy = int( floor( position.y ) ).toVar();
	const iz = int( floor( position.z ) ).toVar();
	const seed = uint( 0xdeadbeef + ( 4 << 2 ) + 13 ).toVar();
	const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
	a.assign( b.assign( c.assign( seed ) ) );
	a.addAssign( uint( ix ) );
	b.addAssign( uint( iy ) );
	c.addAssign( uint( iz ) );

	const mixed = uvec3( mx_bjmix( a, b, c ) ).toVar();
	const hash0 = mx_bjfinal( mixed.x, mixed.y, mixed.z );
	const hash1 = mx_bjfinal( add( mixed.x, uint( 1 ) ), mixed.y, mixed.z );
	const hash2 = mx_bjfinal( add( mixed.x, uint( 2 ) ), mixed.y, mixed.z );

	return vec3(
		mx_bits_to_01( hash0 ),
		mx_bits_to_01( hash1 ),
		mx_bits_to_01( hash2 )
	);

}, { p: 'vec3', return: 'vec3' } );

const mx_cell_noise_vec3_3 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {

	const p = vec4( p_immutable ).toVar();
	const ix = int( mx_floor( p.x ) ).toVar();
	const iy = int( mx_floor( p.y ) ).toVar();
	const iz = int( mx_floor( p.z ) ).toVar();
	const iw = int( mx_floor( p.w ) ).toVar();
	const seed = uint( 0xdeadbeef + ( 5 << 2 ) + 13 ).toVar();
	const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
	a.assign( b.assign( c.assign( seed ) ) );
	a.addAssign( uint( ix ) );
	b.addAssign( uint( iy ) );
	c.addAssign( uint( iz ) );

	const mixed = uvec3( mx_bjmix( a, b, c ) ).toVar();
	a.assign( mixed.x );
	b.assign( mixed.y );
	c.assign( mixed.z );
	a.addAssign( uint( iw ) );

	return vec3(
		mx_bits_to_01( mx_bjfinal( a, b, c ) ),
		mx_bits_to_01( mx_bjfinal( a, add( b, uint( 1 ) ), c ) ),
		mx_bits_to_01( mx_bjfinal( a, add( b, uint( 2 ) ), c ) )
	);

}, { p: 'vec4', return: 'vec3' } );

const mx_cell_noise_vec3$1 = /*@__PURE__*/ overloadingFn( [ mx_cell_noise_vec3_0, mx_cell_noise_vec3_1, mx_cell_noise_vec3_2, mx_cell_noise_vec3_3 ] );

const mx_cell_noise_vec3_3d = /*@__PURE__*/ Fn( ( [ positionInput ] ) => {

	const position = vec3( positionInput ).toVar();
	const ix = int( floor( position.x ) ).toVar();
	const iy = int( floor( position.y ) ).toVar();
	const iz = int( floor( position.z ) ).toVar();
	const seed = uint( 0xdeadbeef + ( 4 << 2 ) + 13 ).toVar();
	const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
	a.assign( b.assign( c.assign( seed ) ) );
	a.addAssign( uint( ix ) );
	b.addAssign( uint( iy ) );
	c.addAssign( uint( iz ) );

	const mixed = uvec3( mx_bjmix( a, b, c ) ).toVar();
	const hash0 = mx_bjfinal( mixed.x, mixed.y, mixed.z );
	const hash1 = mx_bjfinal( add( mixed.x, uint( 1 ) ), mixed.y, mixed.z );
	const hash2 = mx_bjfinal( add( mixed.x, uint( 2 ) ), mixed.y, mixed.z );

	return vec3(
		mx_bits_to_01( hash0 ),
		mx_bits_to_01( hash1 ),
		mx_bits_to_01( hash2 )
	);

}, { position: 'vec3', return: 'vec3' } );

const mx_fractal_noise_float_2d$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => {

	const diminish = float( diminish_immutable ).toVar();
	const lacunarity = float( lacunarity_immutable ).toVar();
	const octaves = int( octaves_immutable ).toVar();
	const p = vec2( p_immutable ).toVar();
	const result = float( 0.0 ).toVar();
	const amplitude = float( 1.0 ).toVar();

	Loop( octaves, () => {

		result.addAssign( amplitude.mul( mx_perlin_noise_float( p ) ) );
		amplitude.mulAssign( diminish );
		p.mulAssign( lacunarity );

	} );

	return result;

}, {
	p: 'vec2',
	octaves: 'int',
	lacunarity: 'float',
	diminish: 'float',
	return: 'float'
} );

const mx_fractal_noise_float$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => {

	const diminish = float( diminish_immutable ).toVar();
	const lacunarity = float( lacunarity_immutable ).toVar();
	const octaves = int( octaves_immutable ).toVar();
	const p = vec3( p_immutable ).toVar();
	const result = float( 0.0 ).toVar();
	const amplitude = float( 1.0 ).toVar();

	Loop( octaves, () => {

		result.addAssign( amplitude.mul( mx_perlin_noise_float( p ) ) );
		amplitude.mulAssign( diminish );
		p.mulAssign( lacunarity );

	} );

	return result;

}, {
	p: 'vec3',
	octaves: 'int',
	lacunarity: 'float',
	diminish: 'float',
	return: 'float'
} );

const mx_fractal_noise_vec3$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => {

	const diminish = float( diminish_immutable ).toVar();
	const lacunarity = float( lacunarity_immutable ).toVar();
	const octaves = int( octaves_immutable ).toVar();
	const p = vec3( p_immutable ).toVar();
	const result = vec3( 0.0 ).toVar();
	const amplitude = float( 1.0 ).toVar();

	Loop( octaves, () => {

		result.addAssign( amplitude.mul( mx_perlin_noise_vec3( p ) ) );
		amplitude.mulAssign( diminish );
		p.mulAssign( lacunarity );

	} );

	return result;

}, {
	p: 'vec3',
	octaves: 'int',
	lacunarity: 'float',
	diminish: 'float',
	return: 'vec3'
} );

const mx_fractal_noise_vec2$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => {

	const diminish = float( diminish_immutable ).toVar();
	const lacunarity = float( lacunarity_immutable ).toVar();
	const octaves = int( octaves_immutable ).toVar();
	const p = vec3( p_immutable ).toVar();

	return vec2( mx_fractal_noise_float$1( p, octaves, lacunarity, diminish ), mx_fractal_noise_float$1( p.add( vec3( int( 19 ), int( 193 ), int( 17 ) ) ), octaves, lacunarity, diminish ) );

}, {
	p: 'vec3',
	octaves: 'int',
	lacunarity: 'float',
	diminish: 'float',
	return: 'vec2'
} );

const mx_fractal_noise_vec4$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => {

	const diminish = float( diminish_immutable ).toVar();
	const lacunarity = float( lacunarity_immutable ).toVar();
	const octaves = int( octaves_immutable ).toVar();
	const p = vec3( p_immutable ).toVar();
	const c = vec3( mx_fractal_noise_vec3$1( p, octaves, lacunarity, diminish ) ).toVar();
	const f = float( mx_fractal_noise_float$1( p.add( vec3( int( 19 ), int( 193 ), int( 17 ) ) ), octaves, lacunarity, diminish ) ).toVar();

	return vec4( c, f );

}, {
	p: 'vec3',
	octaves: 'int',
	lacunarity: 'float',
	diminish: 'float',
	return: 'vec4'
} );

const mx_worley_distance_0 = /*@__PURE__*/ Fn( ( [ p_immutable, x_immutable, y_immutable, xoff_immutable, yoff_immutable, jitter_immutable, metric_immutable ] ) => {

	const metric = int( metric_immutable ).toVar();
	const jitter = float( jitter_immutable ).toVar();
	const yoff = int( yoff_immutable ).toVar();
	const xoff = int( xoff_immutable ).toVar();
	const y = int( y_immutable ).toVar();
	const x = int( x_immutable ).toVar();
	const p = vec2( p_immutable ).toVar();
	const tmp = vec3( mx_cell_noise_vec3$1( vec2( x.add( xoff ), y.add( yoff ) ) ) ).toVar();
	const off = vec2( tmp.x, tmp.y ).toVar();
	off.subAssign( 0.5 );
	off.mulAssign( jitter );
	off.addAssign( 0.5 );
	const cellpos = vec2( vec2( float( x ), float( y ) ).add( off ) ).toVar();
	const diff = vec2( cellpos.sub( p ) ).toVar();

	If( metric.equal( int( 2 ) ), () => {

		return abs( diff.x ).add( abs( diff.y ) );

	} );

	If( metric.equal( int( 3 ) ), () => {

		return max( abs( diff.x ), abs( diff.y ) );

	} );

	return dot( diff, diff );

}, {
	p: 'vec2',
	x: 'int',
	y: 'int',
	xoff: 'int',
	yoff: 'int',
	jitter: 'float',
	metric: 'int',
	return: 'float'
} );

const mx_worley_distance_1 = /*@__PURE__*/ Fn( ( [ p_immutable, x_immutable, y_immutable, z_immutable, xoff_immutable, yoff_immutable, zoff_immutable, jitter_immutable, metric_immutable ] ) => {

	const metric = int( metric_immutable ).toVar();
	const jitter = float( jitter_immutable ).toVar();
	const zoff = int( zoff_immutable ).toVar();
	const yoff = int( yoff_immutable ).toVar();
	const xoff = int( xoff_immutable ).toVar();
	const z = int( z_immutable ).toVar();
	const y = int( y_immutable ).toVar();
	const x = int( x_immutable ).toVar();
	const p = vec3( p_immutable ).toVar();
	const off = vec3( mx_cell_noise_vec3_3d( vec3( x.add( xoff ), y.add( yoff ), z.add( zoff ) ) ) ).toVar();
	off.subAssign( 0.5 );
	off.mulAssign( jitter );
	off.addAssign( 0.5 );
	const cellpos = vec3( vec3( float( x ), float( y ), float( z ) ).add( off ) ).toVar();
	const diff = vec3( cellpos.sub( p ) ).toVar();

	If( metric.equal( int( 2 ) ), () => {

		return abs( diff.x ).add( abs( diff.y ) ).add( abs( diff.z ) );

	} );

	If( metric.equal( int( 3 ) ), () => {

		return max( abs( diff.x ), abs( diff.y ), abs( diff.z ) );

	} );

	return dot( diff, diff );

}, {
	p: 'vec3',
	x: 'int',
	y: 'int',
	z: 'int',
	xoff: 'int',
	yoff: 'int',
	zoff: 'int',
	jitter: 'float',
	metric: 'int',
	return: 'float'
} );

const mx_worley_distance = /*@__PURE__*/ overloadingFn( [ mx_worley_distance_0, mx_worley_distance_1 ] );

const mx_perlin_noise_float_scaled = ( texcoord, amplitude = 1, pivot = 0 ) => mx_perlin_noise_float( texcoord ).mul( amplitude ).add( pivot );

const mx_worley_noise_float_3d$1 = /*@__PURE__*/ Fn( ( [ positionInput, jitterInput, styleInput ] ) => {

	const position = vec3( positionInput ).toVar();
	const jitter = float( jitterInput ).toVar();
	const style = int( styleInput ).toVar();
	const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
	const localpos = vec3( mx_floorfrac( position.x, X ), mx_floorfrac( position.y, Y ), mx_floorfrac( position.z, Z ) ).toVar();
	const sqdist = float( 1e6 ).toVar();
	const minpos = vec3( 0, 0, 0 ).toVar();

	Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {

		Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {

			Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => {

				const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, int( 0 ) ) ).toVar();
				const off = vec3( mx_cell_noise_vec3_3d( vec3( X.add( x ), Y.add( y ), Z.add( z ) ) ) ).toVar();
				off.subAssign( 0.5 );
				off.mulAssign( jitter );
				off.addAssign( 0.5 );
				const cellpos = vec3( vec3( float( x ), float( y ), float( z ) ).add( off ).sub( localpos ) ).toVar();

				If( dist.lessThan( sqdist ), () => {

					sqdist.assign( dist );
					minpos.assign( cellpos );

				} );

			} );

		} );

	} );

	If( style.equal( int( 1 ) ), () => {

		sqdist.assign( mx_cell_noise_float$1( minpos.add( position ) ) );

	} ).Else( () => {

		sqdist.assign( sqrt( sqdist ) );

	} );

	return sqdist;

}, { position: 'vec3', jitter: 'float', style: 'int', return: 'float' } );

const mx_worley_noise_float_2d$1 = /*@__PURE__*/ Fn( ( [ texcoordInput, jitterInput, styleInput ] ) => {

	const texcoord = vec2( texcoordInput ).toVar();
	const jitter = float( jitterInput ).toVar();
	const style = int( styleInput ).toVar();
	const floorPos = floor( texcoord ).toVar();
	const localpos = vec2( fract( texcoord.x ), fract( texcoord.y ) ).toVar();
	const sqdist = float( 1e6 ).toVar();
	const minpos = vec2( 0, 0 ).toVar();

	Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {

		Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {

			const cell = vec2( float( x ), float( y ) ).toVar();
			const seed = vec2( cell.x.add( floorPos.x ), cell.y.add( floorPos.y ) ).toVar();
			const off = vec2( mx_cell_noise_float$1( vec3( seed.x, seed.y, 0 ) ), mx_cell_noise_float$1( vec3( seed.x, seed.y, 1 ) ) ).toVar();
			off.subAssign( 0.5 );
			off.mulAssign( jitter );
			off.addAssign( 0.5 );
			const cellpos = vec2( cell.add( off ).sub( localpos ) ).toVar();
			const dist = dot( cellpos, cellpos ).toVar();

			If( dist.lessThan( sqdist ), () => {

				sqdist.assign( dist );
				minpos.assign( cellpos );

			} );

		} );

	} );

	If( style.equal( int( 1 ) ), () => {

		sqdist.assign( mx_cell_noise_float$1( minpos.add( texcoord ) ) );

	} ).Else( () => {

		sqdist.assign( sqrt( sqdist ) );

	} );

	return sqdist;

}, { texcoord: 'vec2', jitter: 'float', style: 'int', return: 'float' } );

const mx_worley_noise_vec2_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {

	const metric = int( metric_immutable ).toVar();
	const jitter = float( jitter_immutable ).toVar();
	const p = vec2( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar();
	const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar();
	const sqdist = vec2( 1e6, 1e6 ).toVar();

	Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {

		Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {

			const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar();

			If( dist.lessThan( sqdist.x ), () => {

				sqdist.y.assign( sqdist.x );
				sqdist.x.assign( dist );

			} ).ElseIf( dist.lessThan( sqdist.y ), () => {

				sqdist.y.assign( dist );

			} );

		} );

	} );

	If( metric.equal( int( 0 ) ), () => {

		sqdist.assign( sqrt( sqdist ) );

	} );

	return sqdist;

}, { p: 'vec2', jitter: 'float', metric: 'int', return: 'vec2' } );

const mx_worley_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {

	const metric = int( metric_immutable ).toVar();
	const jitter = float( jitter_immutable ).toVar();
	const p = vec2( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar();
	const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar();
	const sqdist = vec3( 1e6, 1e6, 1e6 ).toVar();

	Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {

		Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {

			const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar();

			If( dist.lessThan( sqdist.x ), () => {

				sqdist.z.assign( sqdist.y );
				sqdist.y.assign( sqdist.x );
				sqdist.x.assign( dist );

			} ).ElseIf( dist.lessThan( sqdist.y ), () => {

				sqdist.z.assign( sqdist.y );
				sqdist.y.assign( dist );

			} ).ElseIf( dist.lessThan( sqdist.z ), () => {

				sqdist.z.assign( dist );

			} );

		} );

	} );

	If( metric.equal( int( 0 ) ), () => {

		sqdist.assign( sqrt( sqdist ) );

	} );

	return sqdist;

}, { p: 'vec2', jitter: 'float', metric: 'int', return: 'vec3' } );

const mx_worley_noise_vec2_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {

	const metric = int( metric_immutable ).toVar();
	const jitter = float( jitter_immutable ).toVar();
	const p = vec3( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
	const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar();
	const sqdist = vec2( 1e6, 1e6 ).toVar();

	Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {

		Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {

			Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => {

				const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar();

				If( dist.lessThan( sqdist.x ), () => {

					sqdist.y.assign( sqdist.x );
					sqdist.x.assign( dist );

				} ).ElseIf( dist.lessThan( sqdist.y ), () => {

					sqdist.y.assign( dist );

				} );

			} );

		} );

	} );

	If( metric.equal( int( 0 ) ), () => {

		sqdist.assign( sqrt( sqdist ) );

	} );

	return sqdist;

}, { p: 'vec3', jitter: 'float', metric: 'int', return: 'vec2' } );

const mx_worley_noise_vec2$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec2_0, mx_worley_noise_vec2_1 ] );

const mx_worley_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {

	const metric = int( metric_immutable ).toVar();
	const jitter = float( jitter_immutable ).toVar();
	const p = vec3( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
	const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar();
	const sqdist = vec3( 1e6, 1e6, 1e6 ).toVar();

	Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {

		Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {

			Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => {

				const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar();

				If( dist.lessThan( sqdist.x ), () => {

					sqdist.z.assign( sqdist.y );
					sqdist.y.assign( sqdist.x );
					sqdist.x.assign( dist );

				} ).ElseIf( dist.lessThan( sqdist.y ), () => {

					sqdist.z.assign( sqdist.y );
					sqdist.y.assign( dist );

				} ).ElseIf( dist.lessThan( sqdist.z ), () => {

					sqdist.z.assign( dist );

				} );

			} );

		} );

	} );

	If( metric.equal( int( 0 ) ), () => {

		sqdist.assign( sqrt( sqdist ) );

	} );

	return sqdist;

}, { p: 'vec3', jitter: 'float', metric: 'int', return: 'vec3' } );

const mx_worley_noise_vec3$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec3_0, mx_worley_noise_vec3_1 ] );

const mx_worley_noise_vec3_style_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, style_immutable, metric_immutable ] ) => {

	const metric = int( metric_immutable ).toVar();
	const style = int( style_immutable ).toVar();
	const jitter = float( jitter_immutable ).toVar();
	const p = vec2( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar();
	const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar();
	const sqdist = float( 1e6 ).toVar();
	const minpos = vec2( 0, 0 ).toVar();

	Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {

		Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {

			const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar();
			const tmp = vec3( mx_cell_noise_vec3$1( vec2( X.add( x ), Y.add( y ) ) ) ).toVar();
			const off = vec2( tmp.x, tmp.y ).toVar();
			off.subAssign( 0.5 );
			off.mulAssign( jitter );
			off.addAssign( 0.5 );
			const cellpos = vec2( vec2( float( x ), float( y ) ).add( off ).sub( localpos ) ).toVar();

			If( dist.lessThan( sqdist ), () => {

				sqdist.assign( dist );
				minpos.assign( cellpos );

			} );

		} );

	} );

	const result = vec3( mx_worley_noise_vec3$1( p, jitter, metric ) ).toVar();
	If( style.equal( int( 1 ) ), () => {

		result.assign( mx_cell_noise_vec3$1( minpos.add( p ) ) );

	} );

	return result;

}, {
	p: 'vec2',
	jitter: 'float',
	style: 'int',
	metric: 'int',
	return: 'vec3'
} );

const mx_worley_noise_vec3_style_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, style_immutable, metric_immutable ] ) => {

	const metric = int( metric_immutable ).toVar();
	const style = int( style_immutable ).toVar();
	const jitter = float( jitter_immutable ).toVar();
	const p = vec3( p_immutable ).toVar();
	const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
	const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar();
	const sqdist = float( 1e6 ).toVar();
	const minpos = vec3( 0, 0, 0 ).toVar();

	Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {

		Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {

			Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => {

				const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar();
				const off = vec3( mx_cell_noise_vec3_3d( vec3( X.add( x ), Y.add( y ), Z.add( z ) ) ) ).toVar();
				off.subAssign( 0.5 );
				off.mulAssign( jitter );
				off.addAssign( 0.5 );
				const cellpos = vec3( vec3( float( x ), float( y ), float( z ) ).add( off ).sub( localpos ) ).toVar();

				If( dist.lessThan( sqdist ), () => {

					sqdist.assign( dist );
					minpos.assign( cellpos );

				} );

			} );

		} );

	} );

	const result = vec3( mx_worley_noise_vec3$1( p, jitter, metric ) ).toVar();
	If( style.equal( int( 1 ) ), () => {

		result.assign( mx_cell_noise_vec3_3d( minpos.add( p ) ) );

	} );

	return result;

}, {
	p: 'vec3',
	jitter: 'float',
	style: 'int',
	metric: 'int',
	return: 'vec3'
} );

const mx_worley_noise_vec3_style$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec3_style_0, mx_worley_noise_vec3_style_1 ] );

// Unified Noise 2D
const mx_unifiednoise2d$1 = /*@__PURE__*/ Fn( ( [
	noiseTypeInput,
	texcoordInput,
	freqInput,
	offsetInput,
	jitterInput,
	outminInput,
	outmaxInput,
	clampoutputInput,
	octavesInput,
	lacunarityInput,
	diminishInput,
	styleInput
] ) => {

	const noiseType = int( noiseTypeInput ).toVar();
	const texcoord = vec2( texcoordInput ).toVar();
	const freq = vec2( freqInput ).toVar();
	const offset = vec2( offsetInput ).toVar();
	const jitter = float( jitterInput ).toVar();
	const outmin = float( outminInput ).toVar();
	const outmax = float( outmaxInput ).toVar();
	const clampoutput = float( clampoutputInput ).toVar();
	const octaves = int( octavesInput ).toVar();
	const lacunarity = float( lacunarityInput ).toVar();
	const diminish = float( diminishInput ).toVar();
	const style = int( styleInput ).toVar();

	const applyFreq = mul( texcoord, freq ).toVar();
	const applyOffset = add( applyFreq, offset ).toVar();
	const cellJitterMult = mul( sub( jitter, 1 ), 90000 ).toVar();
	const applyCellJitter = mx_rotate2d( applyOffset, cellJitterMult ).toVar();
	const fractalInput = vec3( applyOffset.x, applyOffset.y, cellJitterMult ).toVar();
	const result = float( 0 ).toVar();

	If( noiseType.equal( int( 0 ) ), () => {

		result.assign( mx_perlin_noise_float_scaled( applyCellJitter, 0.5, 0.5 ) );

	} );
	If( noiseType.equal( int( 1 ) ), () => {

		result.assign( mx_cell_noise_float$1( applyCellJitter ) );

	} );
	If( noiseType.equal( int( 2 ) ), () => {

		result.assign( mx_worley_noise_float_2d$1( applyOffset, jitter, style ) );

	} );
	If( noiseType.equal( int( 3 ) ), () => {

		result.assign( mx_fractal_noise_float$1( fractalInput, octaves, lacunarity, diminish ) );

	} );

	const ranged = add( outmin, mul( result, sub( outmax, outmin ) ) ).toVar();
	const clamped = clamp( ranged, outmin, outmax ).toVar();
	const output = ranged.toVar();

	If( clampoutput.equal( float( 1 ) ), () => {

		output.assign( clamped );

	} );

	return output;

}, {
	noiseType: 'int',
	texcoord: 'vec2',
	freq: 'vec2',
	offset: 'vec2',
	jitter: 'float',
	outmin: 'float',
	outmax: 'float',
	clampoutput: 'float',
	octaves: 'int',
	lacunarity: 'float',
	diminish: 'float',
	style: 'int',
	return: 'float'
} );

// Unified Noise 3D
const mx_unifiednoise3d$1 = /*@__PURE__*/ Fn( ( [
	noiseTypeInput,
	positionInput,
	freqInput,
	offsetInput,
	jitterInput,
	outminInput,
	outmaxInput,
	clampoutputInput,
	octavesInput,
	lacunarityInput,
	diminishInput,
	styleInput
] ) => {

	const noiseType = int( noiseTypeInput ).toVar();
	const position = vec3( positionInput ).toVar();
	const freq = vec3( freqInput ).toVar();
	const offset = vec3( offsetInput ).toVar();
	const jitter = float( jitterInput ).toVar();
	const outmin = float( outminInput ).toVar();
	const outmax = float( outmaxInput ).toVar();
	const clampoutput = float( clampoutputInput ).toVar();
	const octaves = int( octavesInput ).toVar();
	const lacunarity = float( lacunarityInput ).toVar();
	const diminish = float( diminishInput ).toVar();
	const style = int( styleInput ).toVar();

	const applyFreq = mul( position, freq ).toVar();
	const applyOffset = add( applyFreq, offset ).toVar();
	const cellJitterMult = mul( sub( jitter, 1 ), 90000 ).toVar();
	const applyCellJitter = mx_rotate3d( applyOffset, cellJitterMult, vec3( 0.1, 1, 0 ) ).toVar();
	const perlin = mx_perlin_noise_float_scaled( applyCellJitter, 0.5, 0.5 );
	const cell = mx_cell_noise_float$1( applyCellJitter );
	const worley = mx_worley_noise_float_3d$1( applyOffset, jitter, style );
	const fractal = mx_fractal_noise_float$1( applyCellJitter, octaves, lacunarity, diminish );
	const result = perlin.toVar();

	If( noiseType.equal( int( 1 ) ), () => {

		result.assign( cell );

	} );
	If( noiseType.equal( int( 2 ) ), () => {

		result.assign( worley );

	} );
	If( noiseType.equal( int( 3 ) ), () => {

		result.assign( fractal );

	} );

	const ranged = add( outmin, mul( result, sub( outmax, outmin ) ) ).toVar();
	const clamped = clamp( ranged, outmin, outmax ).toVar();
	const output = ranged.toVar();

	If( clampoutput.equal( float( 1 ) ), () => {

		output.assign( clamped );

	} );

	return output;

}, {
	noiseType: 'int',
	position: 'vec3',
	freq: 'vec3',
	offset: 'vec3',
	jitter: 'float',
	outmin: 'float',
	outmax: 'float',
	clampoutput: 'float',
	octaves: 'int',
	lacunarity: 'float',
	diminish: 'float',
	style: 'int',
	return: 'float'
} );

const mx_hsvtorgb = /*@__PURE__*/ Fn( ( [ hsv ] ) => {

	const s = hsv.y;
	const v = hsv.z;

	const result = vec3().toVar();

	If( s.lessThan( 0.0001 ), () => {

		result.assign( vec3( v, v, v ) );

	} ).Else( () => {

		let h = hsv.x;
		h = h.sub( floor( h ) ).mul( 6.0 ).toVar(); // TODO: check what .toVar() is needed in node system cache
		const hi = int( trunc( h ) );
		const f = h.sub( float( hi ) );
		const p = v.mul( s.oneMinus() );
		const q = v.mul( s.mul( f ).oneMinus() );
		const t = v.mul( s.mul( f.oneMinus() ).oneMinus() );

		If( hi.equal( int( 0 ) ), () => {

			result.assign( vec3( v, t, p ) );

		} ).ElseIf( hi.equal( int( 1 ) ), () => {

			result.assign( vec3( q, v, p ) );

		} ).ElseIf( hi.equal( int( 2 ) ), () => {

			result.assign( vec3( p, v, t ) );

		} ).ElseIf( hi.equal( int( 3 ) ), () => {

			result.assign( vec3( p, q, v ) );

		} ).ElseIf( hi.equal( int( 4 ) ), () => {

			result.assign( vec3( t, p, v ) );

		} ).Else( () => {

			result.assign( vec3( v, p, q ) );

		} );

	} );

	return result;

}, { hsv: 'vec3', return: 'vec3' } );

const mx_rgbtohsv = /*@__PURE__*/ Fn( ( [ c_immutable ] ) => {

	const c = vec3( c_immutable ).toVar();
	const r = float( c.x ).toVar();
	const g = float( c.y ).toVar();
	const b = float( c.z ).toVar();
	const mincomp = float( min( r, min( g, b ) ) ).toVar();
	const maxcomp = float( max( r, max( g, b ) ) ).toVar();
	const delta = float( maxcomp.sub( mincomp ) ).toVar();
	const h = float().toVar(), s = float().toVar(), v = float().toVar();
	v.assign( maxcomp );

	If( maxcomp.greaterThan( 0.0 ), () => {

		s.assign( delta.div( maxcomp ) );

	} ).Else( () => {

		s.assign( 0.0 );

	} );

	If( s.lessThanEqual( 0.0 ), () => {

		h.assign( 0.0 );

	} ).Else( () => {

		If( r.greaterThanEqual( maxcomp ), () => {

			h.assign( g.sub( b ).div( delta ) );

		} ).ElseIf( g.greaterThanEqual( maxcomp ), () => {

			h.assign( add( 2.0, b.sub( r ).div( delta ) ) );

		} ).Else( () => {

			h.assign( add( 4.0, r.sub( g ).div( delta ) ) );

		} );

		h.mulAssign( 1.0 / 6.0 );

		If( h.lessThan( 0.0 ), () => {

			h.addAssign( 1.0 );

		} );

	} );

	return vec3( h, s, v );

}, { c: 'vec3', return: 'vec3' } );

const mx_srgb_texture_to_lin_rec709 = /*@__PURE__*/ Fn( ( [ color_immutable ] ) => {

	const color = vec3( color_immutable ).toVar();
	const isAbove = bvec3( greaterThan( color, vec3( 0.04045 ) ) ).toVar();
	const linSeg = vec3( color.div( 12.92 ) ).toVar();
	const powSeg = vec3( pow( max( color.add( vec3( 0.055 ) ), vec3( 0.0 ) ).div( 1.055 ), vec3( 2.4 ) ) ).toVar();

	return mix( linSeg, powSeg, isAbove );

}, { color: 'vec3', return: 'vec3' } );

const mx_aastep = ( threshold, value ) => {

	threshold = float( threshold );
	value = float( value );

	const afwidth = vec2( value.dFdx(), value.dFdy() ).length().mul( 0.70710678118654757 );

	return smoothstep( threshold.sub( afwidth ), threshold.add( afwidth ), value );

};

const _ramp = ( a, b, uv, p ) => mix( a, b, uv[ p ].clamp() );
const mx_ramplr = ( valuel, valuer, texcoord = uv$1() ) => _ramp( valuel, valuer, texcoord, 'x' );
const mx_ramptb = ( valueb, valuet, texcoord = uv$1() ) => _ramp( valueb, valuet, texcoord, 'y' );

// Bilinear ramp: interpolate between four corners (tl, tr, bl, br) using texcoord.x and texcoord.y
const mx_ramp4 = (
	valuetl, valuetr, valuebl, valuebr, texcoord = uv$1()
) => {

	const u = texcoord.x.clamp();
	const v = texcoord.y.clamp();
	const top = mix( valuetl, valuetr, u );
	const bottom = mix( valuebl, valuebr, u );
	return mix( bottom, top, v );

};

const _split = ( a, b, center, uv, p ) => mix( a, b, mx_aastep( center, uv[ p ] ) );
const mx_splitlr = ( valuel, valuer, center, texcoord = uv$1() ) => _split( valuel, valuer, center, texcoord, 'x' );
const mx_splittb = ( valueb, valuet, center, texcoord = uv$1() ) => _split( valueb, valuet, center, texcoord, 'y' );

const mx_transform_uv = ( uv_scale = 1, uv_offset = 0, uv_geo = uv$1() ) => uv_geo.mul( uv_scale ).add( uv_offset );

const mx_safepower = ( in1, in2 = 1 ) => {

	in1 = float( in1 );

	return in1.abs().pow( in2 ).mul( in1.sign() );

};

const mx_contrast = ( input, amount = 1, pivot = .5 ) => float( input ).sub( pivot ).mul( amount ).add( pivot );

const mx_noise_float = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_float( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot );
//export const mx_noise_vec2 = ( texcoord = uv(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_vec3( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot );
const mx_noise_vec3 = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_vec3( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot );
const mx_perlin_noise_vec4_0 = /*@__PURE__*/ Fn( ( [ p ] ) => vec4( mx_perlin_noise_vec3( p ), mx_perlin_noise_float( p.add( vec2( 19, 73 ) ) ) ), { p: 'vec2', return: 'vec4' } );
const mx_perlin_noise_vec4_1 = /*@__PURE__*/ Fn( ( [ p ] ) => vec4( mx_perlin_noise_vec3( p ), mx_perlin_noise_float( p.add( vec3( 19, 73, 29 ) ) ) ), { p: 'vec3', return: 'vec4' } );
const mx_perlin_noise_vec4 = /*@__PURE__*/ overloadingFn( [ mx_perlin_noise_vec4_0, mx_perlin_noise_vec4_1 ] );

const mx_noise_vec4 = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_vec4( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot );

const mx_smoothstep = ( inNode, low = 0, high = 1 ) => {

	const range = sub( high, low );
	const safeRange = max( abs( range ), float( EPSILON ) );
	const t = clamp( div( sub( inNode, low ), safeRange ), float( 0 ), float( 1 ) );
	const hermite = mul( mul( t, t ), sub( float( 3 ), mul( float( 2 ), t ) ) );
	const fallback = step( high, inNode );
	const useFallback = step( high, low );
	return mix( hermite, fallback, useFallback );

};

const mx_cell_noise_vec3 = ( texcoord = uv$1() ) => mx_cell_noise_vec3$1( texcoord.convert( 'vec2|vec3' ) );
const mx_worley_noise_float_2d = ( texcoord = uv$1(), jitter = 1, style = 0 ) => mx_worley_noise_float_2d$1( texcoord, jitter, style );
const mx_worley_noise_float_3d = ( texcoord = uv$1(), jitter = 1, style = 0 ) => mx_worley_noise_float_3d$1( texcoord, jitter, style );
const mx_unifiednoise2d = ( noiseType, texcoord = uv$1(), freq = vec2( 1, 1 ), offset = vec2( 0, 0 ), jitter = 1, outmin = 0, outmax = 1, clampoutput = false, octaves = 1, lacunarity = 2, diminish = .5, style = 0 ) => mx_unifiednoise2d$1( noiseType, texcoord, freq, offset, jitter, outmin, outmax, clampoutput, octaves, lacunarity, diminish, style );
const mx_unifiednoise3d = ( noiseType, texcoord = uv$1(), freq = vec3( 1, 1, 1 ), offset = vec3( 0, 0, 0 ), jitter = 1, outmin = 0, outmax = 1, clampoutput = false, octaves = 1, lacunarity = 2, diminish = .5, style = 0 ) => mx_unifiednoise3d$1( noiseType, texcoord, freq, offset, jitter, outmin, outmax, clampoutput, octaves, lacunarity, diminish, style );

const mx_worley_noise_float = ( texcoord = uv$1(), jitter = 1, style = 0 ) => mx_worley_noise_float_3d( texcoord.convert( 'vec2|vec3' ), jitter, style );
const mx_worley_noise_vec2 = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_vec2$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) );
const mx_worley_noise_vec3 = ( texcoord = uv$1(), jitter = 1, metric = 1 ) => mx_worley_noise_vec3$1( texcoord.convert( 'vec2|vec3' ), jitter, int( metric ) );
const mx_worley_noise_vec3_style = ( texcoord = uv$1(), jitter = 1, style = 0, metric = 0 ) => mx_worley_noise_vec3_style$1( texcoord.convert( 'vec2|vec3' ), jitter, int( style ), int( metric ) );

const mx_cell_noise_float = ( texcoord = uv$1() ) => mx_cell_noise_float$1( texcoord.convert( 'vec2|vec3' ) );

const mx_fractal_noise_float_2d = ( texcoord = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_float_2d$1( texcoord, int( octaves ), lacunarity, diminish ).mul( amplitude );
const mx_fractal_noise_float = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_float$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude );
const mx_fractal_noise_vec2 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec2$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude );
const mx_fractal_noise_vec3 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec3$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude );
const mx_fractal_noise_vec4 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec4$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude );

// === Moved from MaterialXLoader.js ===

// Math ops
const mx_add = ( in1, in2 = float( 0 ) ) => add( in1, in2 );
const mx_subtract = ( in1, in2 = float( 0 ) ) => sub( in1, in2 );
const mx_multiply = ( in1, in2 = float( 1 ) ) => mul( in1, in2 );
const mx_divide = ( in1, in2 = float( 1 ) ) => div( in1, in2 );
const mx_modulo = ( in1, in2 = float( 1 ) ) => sub( in1, mul( in2, floor( div( in1, in2 ) ) ) );
const mx_power = ( in1, in2 = float( 1 ) ) => pow( in1, in2 );
const mx_atan2 = ( in1 = float( 0 ), in2 = float( 1 ) ) => atan( in1, in2 );
const mx_timer = () => time;
const mx_frame = () => frameId;
const mx_invert = ( in1, amount = float( 1 ) ) => sub( amount, in1 );
const mx_ifgreater = ( value1, value2, in1, in2 ) => value1.greaterThan( value2 ).mix( in2, in1 );
const mx_ifgreatereq = ( value1, value2, in1, in2 ) => value1.greaterThanEqual( value2 ).mix( in2, in1 );
const mx_ifequal = ( value1, value2, in1, in2 ) => value1.equal( value2 ).mix( in2, in1 );

// Enhanced separate node to support multi-output referencing (outx, outy, outz, outw)
const mx_separate = ( in1, channelOrOut = null ) => {

	if ( typeof channelOrOut === 'string' ) {

		const map = { x: 0, r: 0, y: 1, g: 1, z: 2, b: 2, w: 3, a: 3 };
		const c = channelOrOut.replace( /^out/, '' ).toLowerCase();
		if ( map[ c ] !== undefined ) return in1.element( map[ c ] );

	}

	if ( typeof channelOrOut === 'number' ) {

		return in1.element( channelOrOut );

	}

	if ( typeof channelOrOut === 'string' && channelOrOut.length === 1 ) {

		const map = { x: 0, r: 0, y: 1, g: 1, z: 2, b: 2, w: 3, a: 3 };
		if ( map[ channelOrOut ] !== undefined ) return in1.element( map[ channelOrOut ] );

	}

	return in1;

};

const mx_place2d = (
	texcoord, pivot = vec2( 0, 0 ), scale = vec2( 1, 1 ), rotate = float( 0 ), offset = vec2( 0, 0 ), operationorder = int( 0 )
) => {

	const centered = sub( texcoord, pivot );
	const srt = add( sub( mx_rotate2d( div( centered, scale ), rotate ), offset ), pivot );
	const trs = add( div( mx_rotate2d( sub( centered, offset ), rotate ), scale ), pivot );

	if ( typeof operationorder === 'number' ) return Math.abs( operationorder ) > Number.EPSILON ? trs : srt;

	return mix( srt, trs, step( 0.5, float( operationorder ) ) );

};

const mx_heighttonormal = ( input, scale = 1, texcoord = uv$1() ) => {

	const sobelScale = float( 1.0 / 16.0 );
	const height = float( input );
	const uvNode = vec2( texcoord );
	const dHdS = vec2( height.dFdx(), height.dFdy() ).mul( float( scale ) ).mul( sobelScale );
	const dUdS = vec2( uvNode.x.dFdx(), uvNode.x.dFdy() );
	const dVdS = vec2( uvNode.y.dFdx(), uvNode.y.dFdy() );
	const tangent = vec3( dUdS.x, dVdS.x, dHdS.x );
	const bitangent = vec3( dUdS.y, dVdS.y, dHdS.y );
	let n = cross( tangent, bitangent );
	const invalid = dot( n, n ).lessThan( float( 1e-12 ) );
	n = invalid.mix( n, vec3( 0, 0, 1 ) );
	const mirrored = n.z.lessThan( float( 0 ) );
	n = mirrored.mix( n, n.mul( -1 ) );
	return normalize( n ).mul( 0.5 ).add( 0.5 );

};

const EON_EPSILON = 1e-7;
const FON_A_COEFFICIENT = 0.5 - 2 / ( 3 * Math.PI );
const FON_AVERAGE_ALBEDO_COEFFICIENT = 2 / 3 - 28 / ( 15 * Math.PI );

const FON_DirectionalAlbedo = /*@__PURE__*/ Fn( ( { mu, roughness, A } ) => {

	const muComp = mu.oneMinus();
	const gOverPi = muComp.mul(
		muComp.mul(
			muComp.mul(
				muComp.mul( 0.0714429953 ).sub( 0.332181442 )
			).add( 0.491881867 )
		).add( 0.0571085289 )
	);

	return A.mul( roughness.mul( gOverPi ).add( 1.0 ) );

}, { mu: 'float', roughness: 'float', A: 'float', return: 'float' } );

// Portsmouth et al. 2025, "EON: A Practical Energy-Preserving Rough Diffuse BRDF"
// https://jcgt.org/published/0014/01/06/
const BRDF_EON = /*@__PURE__*/ Fn( ( { lightDirection, diffuseColor, roughness, normalView: normalView$1 = normalView, viewDirection = positionViewDirection } ) => {

	const rho = diffuseColor.clamp();
	const dotNL = normalView$1.dot( lightDirection ).clamp();
	const dotNV = normalView$1.dot( viewDirection ).clamp();
	const s = lightDirection.dot( viewDirection ).sub( dotNL.mul( dotNV ) );
	const sOverT = s.greaterThan( 0.0 ).select( s.div( dotNL.max( dotNV ).max( EON_EPSILON ) ), s );

	const A = roughness.mul( FON_A_COEFFICIENT ).add( 1.0 ).reciprocal();
	const singleScatter = rho.mul( 1 / Math.PI, A, roughness.mul( sOverT ).add( 1.0 ) );

	const averageAlbedo = A.mul( roughness.mul( FON_AVERAGE_ALBEDO_COEFFICIENT ).add( 1.0 ) );
	const albedoV = FON_DirectionalAlbedo( { mu: dotNV, roughness, A } );
	const albedoL = FON_DirectionalAlbedo( { mu: dotNL, roughness, A } );
	const rhoMultiScatter = rho.mul( rho, averageAlbedo ).div( rho.mul( averageAlbedo.oneMinus() ).oneMinus().max( EON_EPSILON ) );
	const multiScatter = rhoMultiScatter.mul(
		1 / Math.PI,
		albedoV.oneMinus().max( EON_EPSILON ),
		albedoL.oneMinus().max( EON_EPSILON )
	).div( averageAlbedo.oneMinus().max( EON_EPSILON ) );
	const eon = singleScatter.add( multiScatter );

	return roughness.lessThanEqual( EON_EPSILON ).select( rho.mul( 1 / Math.PI ), eon );

} );

const EON_DirectionalAlbedo = /*@__PURE__*/ Fn( ( { diffuseColor, roughness, dotNV } ) => {

	const rho = diffuseColor.clamp();
	const A = roughness.mul( FON_A_COEFFICIENT ).add( 1.0 ).reciprocal();
	const directionalAlbedo = FON_DirectionalAlbedo( { mu: dotNV.clamp(), roughness, A } );
	const averageAlbedo = A.mul( roughness.mul( FON_AVERAGE_ALBEDO_COEFFICIENT ).add( 1.0 ) );
	const rhoMultiScatter = rho.mul( rho, averageAlbedo ).div( rho.mul( averageAlbedo.oneMinus() ).oneMinus().max( EON_EPSILON ) );
	const eonAlbedo = rho.mul( directionalAlbedo ).add( rhoMultiScatter.mul( directionalAlbedo.oneMinus() ) );

	return roughness.lessThanEqual( EON_EPSILON ).select( rho, eonAlbedo );

}, { diffuseColor: 'vec3', roughness: 'float', dotNV: 'float', return: 'vec3' } );

const F_Schlick = /*@__PURE__*/ Fn( ( { f0, f90, dotVH } ) => {

	// Original approximation by Christophe Schlick '94
	// float fresnel = pow( 1.0 - dotVH, 5.0 );

	// Optimized variant (presented by Epic at SIGGRAPH '13)
	// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
	const fresnel = dotVH.mul( -5.55473 ).sub( 6.98316 ).mul( dotVH ).exp2();

	return f0.mul( fresnel.oneMinus() ).add( f90.mul( fresnel ) );

} ); // validated

// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
const V_GGX_SmithCorrelated = /*@__PURE__*/ Fn( ( { alpha, dotNL, dotNV } ) => {

	const a2 = alpha.pow2();

	const gv = dotNL.mul( a2.add( a2.oneMinus().mul( dotNV.pow2() ) ).sqrt() );
	const gl = dotNV.mul( a2.add( a2.oneMinus().mul( dotNL.pow2() ) ).sqrt() );

	return div( 0.5, gv.add( gl ).max( EPSILON ) );

}, { alpha: 'float', dotNL: 'float', dotNV: 'float', return: 'float' } ); // validated

// https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf

const V_GGX_SmithCorrelated_Anisotropic = /*@__PURE__*/ Fn( ( { alphaT, alphaB, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } ) => {

	const gv = dotNL.mul( vec3( alphaT.mul( dotTV ), alphaB.mul( dotBV ), dotNV ).length() );
	const gl = dotNV.mul( vec3( alphaT.mul( dotTL ), alphaB.mul( dotBL ), dotNL ).length() );

	return div( 0.5, gv.add( gl ).max( EPSILON ) );

}, {
	alphaT: 'float',
	alphaB: 'float',
	dotTV: 'float',
	dotBV: 'float',
	dotTL: 'float',
	dotBL: 'float',
	dotNV: 'float',
	dotNL: 'float',
	return: 'float'
} );

// Microfacet Models for Refraction through Rough Surfaces - equation (33)
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
// alpha is "roughness squared" in Disney’s reparameterization
const D_GGX = /*@__PURE__*/ Fn( ( { alpha, dotNH } ) => {

	const a2 = alpha.pow2();

	const denom = dotNH.pow2().mul( a2.oneMinus() ).oneMinus(); // avoid alpha = 0 with dotNH = 1

	return a2.div( denom.pow2() ).mul( 1 / Math.PI );

}, { alpha: 'float', dotNH: 'float', return: 'float' } ); // validated

const RECIPROCAL_PI = /*@__PURE__*/ float( 1 / Math.PI );

// https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf

const D_GGX_Anisotropic = /*@__PURE__*/ Fn( ( { alphaT, alphaB, dotNH, dotTH, dotBH } ) => {

	const a2 = alphaT.mul( alphaB );
	const v = vec3( alphaB.mul( dotTH ), alphaT.mul( dotBH ), a2.mul( dotNH ) );
	const v2 = v.dot( v );
	const w2 = a2.div( v2 );

	return RECIPROCAL_PI.mul( a2.mul( w2.pow2() ) );

}, {
	alphaT: 'float',
	alphaB: 'float',
	dotNH: 'float',
	dotTH: 'float',
	dotBH: 'float',
	return: 'float'
} );

// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
const BRDF_GGX = /*@__PURE__*/ Fn( ( { lightDirection, f0, f90, roughness, f, normalView: normalView$1 = normalView, viewDirection = positionViewDirection, USE_IRIDESCENCE, USE_ANISOTROPY } ) => {

	const alpha = roughness.max( 0.045 ).pow2(); // punctual lights need a minimum roughness to show a highlight

	const halfDir = lightDirection.add( viewDirection ).normalize();

	const dotNL = normalView$1.dot( lightDirection ).clamp();
	const dotNV = normalView$1.dot( viewDirection ).clamp(); // @ TODO: Move to core dotNV
	const dotNH = normalView$1.dot( halfDir ).clamp();
	const dotVH = viewDirection.dot( halfDir ).clamp();

	let F = F_Schlick( { f0, f90, dotVH } );
	let V, D;

	if ( defined( USE_IRIDESCENCE ) ) {

		F = iridescence.mix( F, f );

	}

	if ( defined( USE_ANISOTROPY ) ) {

		const dotTL = anisotropyT.dot( lightDirection );
		const dotTV = anisotropyT.dot( viewDirection );
		const dotTH = anisotropyT.dot( halfDir );
		const dotBL = anisotropyB.dot( lightDirection );
		const dotBV = anisotropyB.dot( viewDirection );
		const dotBH = anisotropyB.dot( halfDir );

		const clampedAlphaT = alphaT.max( alpha );

		V = V_GGX_SmithCorrelated_Anisotropic( { alphaT: clampedAlphaT, alphaB: alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } );
		D = D_GGX_Anisotropic( { alphaT: clampedAlphaT, alphaB: alpha, dotNH, dotTH, dotBH } );

	} else {

		V = V_GGX_SmithCorrelated( { alpha, dotNL, dotNV } );
		D = D_GGX( { alpha, dotNH } );

	}

	return F.mul( V ).mul( D );

} ); // validated

const BRDF_Lambert = /*@__PURE__*/ Fn( ( inputs ) => {

	return inputs.diffuseColor.mul( 1 / Math.PI ); // punctual light

} ); // validated

// https://github.com/google/filament/blob/master/shaders/src/brdf.fs
const D_Charlie = /*@__PURE__*/ Fn( ( { roughness, dotNH } ) => {

	const alpha = roughness.pow2();

	// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF"
	const invAlpha = float( 1.0 ).div( alpha );
	const cos2h = dotNH.pow2();
	const sin2h = cos2h.oneMinus().max( 0.0078125 ); // 2^(-14/2), so sin2h^2 > 0 in fp16

	return float( 2.0 ).add( invAlpha ).mul( sin2h.pow( invAlpha.mul( 0.5 ) ) ).div( 2.0 * Math.PI );

}, { roughness: 'float', dotNH: 'float', return: 'float' } );

// https://github.com/google/filament/blob/master/shaders/src/brdf.fs
const V_Neubelt = /*@__PURE__*/ Fn( ( { dotNV, dotNL } ) => {

	// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
	return float( 1.0 ).div( float( 4.0 ).mul( dotNL.add( dotNV ).sub( dotNL.mul( dotNV ) ) ) ).clamp();

}, { dotNV: 'float', dotNL: 'float', return: 'float' } );

const BRDF_Sheen = /*@__PURE__*/ Fn( ( { lightDirection } ) => {

	const halfDir = lightDirection.add( positionViewDirection ).normalize();

	const dotNL = normalView.dot( lightDirection ).clamp();
	const dotNV = normalView.dot( positionViewDirection ).clamp();
	const dotNH = normalView.dot( halfDir ).clamp();

	const D = D_Charlie( { roughness: sheenRoughness, dotNH } );
	const V = V_Neubelt( { dotNV, dotNL } );

	return sheen.mul( D ).mul( V );

} );

/**
 * Precomputed DFG LUT for physically based specular lighting, used by both
 * image-based lighting and direct-light multi-scattering energy compensation
 * Resolution: 16x16
 * Samples: 4096 per texel
 * Format: RG16F (2 half floats per texel: scale, bias)
 */

const DATA = new Uint16Array( [
	0x30b5, 0x3ad1, 0x314c, 0x3a4d, 0x33d2, 0x391c, 0x35ef, 0x3828, 0x37f3, 0x36a6, 0x38d1, 0x3539, 0x3979, 0x3410, 0x39f8, 0x3252, 0x3a53, 0x30f0, 0x3a94, 0x2fc9, 0x3abf, 0x2e35, 0x3ada, 0x2d05, 0x3ae8, 0x2c1f, 0x3aed, 0x2ae0, 0x3aea, 0x29d1, 0x3ae1, 0x28ff,
	0x3638, 0x38e4, 0x364a, 0x38ce, 0x3699, 0x385e, 0x374e, 0x372c, 0x3839, 0x35a4, 0x38dc, 0x3462, 0x396e, 0x32c4, 0x39de, 0x3134, 0x3a2b, 0x3003, 0x3a59, 0x2e3a, 0x3a6d, 0x2ce1, 0x3a6e, 0x2bba, 0x3a5f, 0x2a33, 0x3a49, 0x290a, 0x3a2d, 0x2826, 0x3a0a, 0x26e8,
	0x3894, 0x36d7, 0x3897, 0x36c9, 0x38a3, 0x3675, 0x38bc, 0x35ac, 0x38ee, 0x349c, 0x393e, 0x3332, 0x3997, 0x3186, 0x39e2, 0x3038, 0x3a13, 0x2e75, 0x3a29, 0x2cf5, 0x3a2d, 0x2bac, 0x3a21, 0x29ff, 0x3a04, 0x28bc, 0x39dc, 0x2790, 0x39ad, 0x261a, 0x3978, 0x24fa,
	0x39ac, 0x34a8, 0x39ac, 0x34a3, 0x39ae, 0x3480, 0x39ae, 0x3423, 0x39b1, 0x330e, 0x39c2, 0x31a9, 0x39e0, 0x3063, 0x39fc, 0x2eb5, 0x3a0c, 0x2d1d, 0x3a14, 0x2bcf, 0x3a07, 0x29ff, 0x39e9, 0x28a3, 0x39be, 0x273c, 0x3989, 0x25b3, 0x394a, 0x2488, 0x3907, 0x2345,
	0x3a77, 0x3223, 0x3a76, 0x321f, 0x3a73, 0x3204, 0x3a6a, 0x31b3, 0x3a58, 0x3114, 0x3a45, 0x303b, 0x3a34, 0x2eb6, 0x3a26, 0x2d31, 0x3a1e, 0x2bef, 0x3a0b, 0x2a0d, 0x39ec, 0x28a1, 0x39c0, 0x271b, 0x3987, 0x2580, 0x3944, 0x2449, 0x38fa, 0x22bd, 0x38ac, 0x2155,
	0x3b07, 0x2fca, 0x3b06, 0x2fca, 0x3b00, 0x2fb8, 0x3af4, 0x2f7c, 0x3adb, 0x2eea, 0x3ab4, 0x2e00, 0x3a85, 0x2cec, 0x3a5e, 0x2bc5, 0x3a36, 0x2a00, 0x3a0d, 0x2899, 0x39dc, 0x2707, 0x39a0, 0x2562, 0x395a, 0x2424, 0x390b, 0x2268, 0x38b7, 0x20fd, 0x385f, 0x1fd1,
	0x3b69, 0x2cb9, 0x3b68, 0x2cbb, 0x3b62, 0x2cbb, 0x3b56, 0x2cae, 0x3b3b, 0x2c78, 0x3b0d, 0x2c0a, 0x3acf, 0x2ae3, 0x3a92, 0x2998, 0x3a54, 0x2867, 0x3a17, 0x26d0, 0x39d3, 0x253c, 0x3989, 0x2402, 0x3935, 0x2226, 0x38dc, 0x20bd, 0x387d, 0x1f54, 0x381d, 0x1db3,
	0x3ba9, 0x296b, 0x3ba8, 0x296f, 0x3ba3, 0x297b, 0x3b98, 0x2987, 0x3b7f, 0x2976, 0x3b4e, 0x2927, 0x3b0e, 0x2895, 0x3ac2, 0x27b7, 0x3a73, 0x263b, 0x3a23, 0x24e7, 0x39d0, 0x239b, 0x3976, 0x21d9, 0x3917, 0x207e, 0x38b2, 0x1ee7, 0x384b, 0x1d53, 0x37c7, 0x1c1e,
	0x3bd2, 0x25cb, 0x3bd1, 0x25d3, 0x3bcd, 0x25f0, 0x3bc2, 0x261f, 0x3bad, 0x2645, 0x3b7d, 0x262d, 0x3b3e, 0x25c4, 0x3aec, 0x250f, 0x3a93, 0x243a, 0x3a32, 0x22ce, 0x39d0, 0x215b, 0x3969, 0x202a, 0x38fe, 0x1e6e, 0x388f, 0x1cf1, 0x381f, 0x1b9b, 0x3762, 0x19dd,
	0x3be9, 0x21ab, 0x3be9, 0x21b7, 0x3be5, 0x21e5, 0x3bdd, 0x2241, 0x3bc9, 0x22a7, 0x3ba0, 0x22ec, 0x3b62, 0x22cd, 0x3b0f, 0x2247, 0x3aae, 0x2175, 0x3a44, 0x2088, 0x39d4, 0x1f49, 0x3960, 0x1dbe, 0x38e9, 0x1c77, 0x3870, 0x1ae8, 0x37f1, 0x1953, 0x3708, 0x181b,
	0x3bf6, 0x1cea, 0x3bf6, 0x1cfb, 0x3bf3, 0x1d38, 0x3bec, 0x1dbd, 0x3bda, 0x1e7c, 0x3bb7, 0x1f25, 0x3b7d, 0x1f79, 0x3b2c, 0x1f4c, 0x3ac6, 0x1ea6, 0x3a55, 0x1dbb, 0x39da, 0x1cbd, 0x395a, 0x1b9d, 0x38d8, 0x1a00, 0x3855, 0x18ac, 0x37ab, 0x173c, 0x36b7, 0x1598,
	0x3bfc, 0x1736, 0x3bfc, 0x1759, 0x3bf9, 0x17e7, 0x3bf4, 0x1896, 0x3be4, 0x1997, 0x3bc6, 0x1aa8, 0x3b91, 0x1b84, 0x3b43, 0x1bd2, 0x3ade, 0x1b8a, 0x3a65, 0x1acd, 0x39e2, 0x19d3, 0x3957, 0x18cd, 0x38ca, 0x17b3, 0x383e, 0x1613, 0x376d, 0x14bf, 0x366f, 0x135e,
	0x3bff, 0x101b, 0x3bff, 0x1039, 0x3bfc, 0x10c8, 0x3bf9, 0x1226, 0x3bea, 0x1428, 0x3bcf, 0x1584, 0x3b9f, 0x16c5, 0x3b54, 0x179a, 0x3af0, 0x17ce, 0x3a76, 0x1771, 0x39ea, 0x16a4, 0x3956, 0x15a7, 0x38bf, 0x14a7, 0x3829, 0x1379, 0x3735, 0x11ea, 0x362d, 0x10a1,
	0x3c00, 0x061b, 0x3c00, 0x066a, 0x3bfe, 0x081c, 0x3bfa, 0x0a4c, 0x3bed, 0x0d16, 0x3bd5, 0x0fb3, 0x3ba9, 0x114d, 0x3b63, 0x127c, 0x3b01, 0x132f, 0x3a85, 0x1344, 0x39f4, 0x12d2, 0x3957, 0x120d, 0x38b5, 0x1122, 0x3817, 0x103c, 0x3703, 0x0ed3, 0x35f0, 0x0d6d,
	0x3c00, 0x007a, 0x3c00, 0x0089, 0x3bfe, 0x011d, 0x3bfb, 0x027c, 0x3bf0, 0x04fa, 0x3bda, 0x0881, 0x3bb1, 0x0acd, 0x3b6f, 0x0c97, 0x3b10, 0x0d7b, 0x3a93, 0x0df1, 0x39fe, 0x0def, 0x3959, 0x0d8a, 0x38af, 0x0ce9, 0x3808, 0x0c31, 0x36d5, 0x0af0, 0x35b9, 0x09a3,
	0x3c00, 0x0000, 0x3c00, 0x0001, 0x3bff, 0x0015, 0x3bfb, 0x0059, 0x3bf2, 0x00fd, 0x3bdd, 0x01df, 0x3bb7, 0x031c, 0x3b79, 0x047c, 0x3b1d, 0x05d4, 0x3aa0, 0x06d5, 0x3a08, 0x075a, 0x395d, 0x075e, 0x38aa, 0x06f7, 0x37f4, 0x0648, 0x36ac, 0x0576, 0x3586, 0x049f
] );

let lut = null;

const DFGLUT = /*@__PURE__*/ Fn( ( { roughness, dotNV } ) => {

	if ( lut === null ) {

		lut = new DataTexture( DATA, 16, 16, RGFormat, HalfFloatType );
		lut.name = 'DFG_LUT';
		lut.minFilter = LinearFilter;
		lut.magFilter = LinearFilter;
		lut.wrapS = ClampToEdgeWrapping;
		lut.wrapT = ClampToEdgeWrapping;
		lut.generateMipmaps = false;
		lut.needsUpdate = true;

	}

	const uv = vec2( roughness, dotNV );

	return texture( lut, uv ).rg;

} );

const EnvironmentBRDF = /*@__PURE__*/ Fn( ( inputs ) => {

	const { dotNV, specularColor, specularF90, roughness } = inputs;

	const fab = DFGLUT( { dotNV, roughness } );
	return specularColor.mul( fab.x ).add( specularF90.mul( fab.y ) );

} );

const Schlick_to_F0 = /*@__PURE__*/ Fn( ( { f, f90, dotVH } ) => {

	const x = dotVH.oneMinus().saturate();
	const x2 = x.mul( x );
	const x5 = x.mul( x2, x2 ).clamp( 0, .9999 );

	return f.sub( vec3( f90 ).mul( x5 ) ).div( x5.oneMinus() );

}, { f: 'vec3', f90: 'float', dotVH: 'float', return: 'vec3' } );

// Rect Area Light

// Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
// by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt
// code: https://github.com/selfshadow/ltc_code/

const LTC_Uv = /*@__PURE__*/ Fn( ( { N, V, roughness } ) => {

	const LUT_SIZE = 64.0;
	const LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;
	const LUT_BIAS = 0.5 / LUT_SIZE;

	const dotNV = N.dot( V ).saturate();

	// texture parameterized by sqrt( GGX alpha ) and sqrt( 1 - cos( theta ) )
	const uv = vec2( roughness, dotNV.oneMinus().sqrt() );

	uv.assign( uv.mul( LUT_SCALE ).add( LUT_BIAS ) );

	return uv;

}, { N: 'vec3', V: 'vec3', roughness: 'float', return: 'vec2' } );

const LTC_ClippedSphereFormFactor = /*@__PURE__*/ Fn( ( { f } ) => {

	// Real-Time Area Lighting: a Journey from Research to Production (p.102)
	// An approximation of the form factor of a horizon-clipped rectangle.

	const l = f.length();

	return max( l.mul( l ).add( f.z ).div( l.add( 1.0 ) ), 0 );

}, { f: 'vec3', return: 'float' } );

const LTC_EdgeVectorFormFactor = /*@__PURE__*/ Fn( ( { v1, v2 } ) => {

	const x = v1.dot( v2 );
	const y = x.abs().toVar();

	// rational polynomial approximation to theta / sin( theta ) / 2PI
	const a = y.mul( 0.0145206 ).add( 0.4965155 ).mul( y ).add( 0.8543985 ).toVar();
	const b = y.add( 4.1616724 ).mul( y ).add( 3.4175940 ).toVar();
	const v = a.div( b );

	const theta_sintheta = x.greaterThan( 0.0 ).select( v, max( x.mul( x ).oneMinus(), 1e-7 ).inverseSqrt().mul( 0.5 ).sub( v ) );

	return v1.cross( v2 ).mul( theta_sintheta );

}, { v1: 'vec3', v2: 'vec3', return: 'vec3' } );

const LTC_Evaluate = /*@__PURE__*/ Fn( ( { N, V, P, mInv, p0, p1, p2, p3 } ) => {

	// bail if point is on back side of plane of light
	// assumes ccw winding order of light vertices
	const v1 = p1.sub( p0 ).toVar();
	const v2 = p3.sub( p0 ).toVar();

	const lightNormal = v1.cross( v2 );
	const result = vec3().toVar();

	If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => {

		// construct orthonormal basis around N
		const T1 = V.sub( N.mul( V.dot( N ) ) ).normalize();
		const T2 = N.cross( T1 ).negate(); // negated from paper; possibly due to a different handedness of world coordinate system

		// compute transform
		const mat = mInv.mul( mat3( T1, T2, N ).transpose() ).toVar();

		// transform rect
		// & project rect onto sphere
		const coords0 = mat.mul( p0.sub( P ) ).normalize().toVar();
		const coords1 = mat.mul( p1.sub( P ) ).normalize().toVar();
		const coords2 = mat.mul( p2.sub( P ) ).normalize().toVar();
		const coords3 = mat.mul( p3.sub( P ) ).normalize().toVar();

		// calculate vector form factor
		const vectorFormFactor = vec3( 0 ).toVar();
		vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) );
		vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) );
		vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) );
		vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) );

		// adjust for horizon clipping
		result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor } ) ) );

	} );

	return result;

}, {
	N: 'vec3',
	V: 'vec3',
	P: 'vec3',
	mInv: 'mat3',
	p0: 'vec3',
	p1: 'vec3',
	p2: 'vec3',
	p3: 'vec3',
	return: 'vec3'
} );

const LTC_Evaluate_Volume = /*@__PURE__*/ Fn( ( { P, p0, p1, p2, p3 } ) => {

	// bail if point is on back side of plane of light
	// assumes ccw winding order of light vertices
	const v1 = p1.sub( p0 ).toVar();
	const v2 = p3.sub( p0 ).toVar();

	const lightNormal = v1.cross( v2 );
	const result = vec3().toVar();

	If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => {

		// transform rect
		// & project rect onto sphere
		const coords0 = p0.sub( P ).normalize().toVar();
		const coords1 = p1.sub( P ).normalize().toVar();
		const coords2 = p2.sub( P ).normalize().toVar();
		const coords3 = p3.sub( P ).normalize().toVar();

		// calculate vector form factor
		const vectorFormFactor = vec3( 0 ).toVar();
		vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) );
		vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) );
		vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) );
		vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) );

		// adjust for horizon clipping
		result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor.abs() } ) ) );

	} );

	return result;

}, {
	P: 'vec3',
	p0: 'vec3',
	p1: 'vec3',
	p2: 'vec3',
	p3: 'vec3',
	return: 'vec3'
} );

const getGeometryRoughness = /*@__PURE__*/ Fn( ( builder ) => {

	if ( builder.geometry.hasAttribute( 'normal' ) === false ) {

		return float( 0 );

	}

	const dxy = normalViewGeometry.dFdx().abs().max( normalViewGeometry.dFdy().abs() );
	const geometryRoughness = dxy.x.max( dxy.y ).max( dxy.z );

	return geometryRoughness;

} );

/**
 * This computes a parallax corrected normal which is used for box-projected cube mapping (BPCEM).
 *
 * Reference: {@link https://devlog-martinsh.blogspot.com/2011/09/box-projected-cube-environment-mapping.html}
 *
 * ```js
 * const uvNode = getParallaxCorrectNormal( reflectVector, vec3( 200, 100, 100 ), vec3( 0, - 50, 0 ) );
 * material.envNode = pmremTexture( renderTarget.texture, uvNode );
 * ```
 *
 * @tsl
 * @function
 * @param {Node<vec3>} normal - The normal to correct.
 * @param {Node<vec3>} cubeSize - The cube size should reflect the size of the environment (BPCEM is usually applied in closed environments like rooms).
 * @param {Node<vec3>} cubePos - The cube position.
 * @return {Node<vec3>} The parallax corrected normal.
 */
const getParallaxCorrectNormal = /*@__PURE__*/ Fn( ( [ normal, cubeSize, cubePos ] ) => {

	const nDir = normalize( normal ).toConst();
	const positive = nDir.greaterThanEqual( 0 );

	// keep axis-aligned directions away from zero so the distances stay finite
	const safeDir = positive.select( nDir.max( EPSILON ), nDir.min( EPSILON.negate() ) );
	const extent = cubeSize.mul( 0.5 ).toConst();
	const distances = positive.select( extent, extent.negate() ).add( cubePos ).sub( positionWorld ).div( safeDir ).toConst();

	const correction = min( distances.x, distances.y, distances.z ).toConst();
	return positionWorld.add( nDir.mul( correction ) ).sub( cubePos );

} );

const getRoughness = /*@__PURE__*/ Fn( ( inputs ) => {

	const { roughness } = inputs;

	const geometryRoughness = getGeometryRoughness();

	// Minimum roughness, so even a perfect mirror samples a prefiltered level of the environment map.
	// Matches Filament's desktop MIN_PERCEPTUAL_ROUGHNESS: https://github.com/google/filament/blob/main/shaders/src/surface_material.fs
	let roughnessFactor = roughness.max( 0.045 );
	roughnessFactor = roughnessFactor.add( geometryRoughness );
	roughnessFactor = roughnessFactor.min( 1.0 );

	return roughnessFactor;

} );

const getShIrradianceAt = /*@__PURE__*/ Fn( ( [ normal, shCoefficients ] ) => {

	// normal is assumed to have unit length

	const x = normal.x, y = normal.y, z = normal.z;

	// band 0
	let result = shCoefficients.element( 0 ).mul( 0.886227 );

	// band 1
	result = result.add( shCoefficients.element( 1 ).mul( 2.0 * 0.511664 ).mul( y ) );
	result = result.add( shCoefficients.element( 2 ).mul( 2.0 * 0.511664 ).mul( z ) );
	result = result.add( shCoefficients.element( 3 ).mul( 2.0 * 0.511664 ).mul( x ) );

	// band 2
	result = result.add( shCoefficients.element( 4 ).mul( 2.0 * 0.429043 ).mul( x ).mul( y ) );
	result = result.add( shCoefficients.element( 5 ).mul( 2.0 * 0.429043 ).mul( y ).mul( z ) );
	result = result.add( shCoefficients.element( 6 ).mul( z.mul( z ).mul( 0.743125 ).sub( 0.247708 ) ) );
	result = result.add( shCoefficients.element( 7 ).mul( 2.0 * 0.429043 ).mul( x ).mul( z ) );
	result = result.add( shCoefficients.element( 8 ).mul( 0.429043 ).mul( mul( x, x ).sub( mul( y, y ) ) ) );

	return result;

} );

var Three_TSL = /*#__PURE__*/Object.freeze({
	__proto__: null,
	BRDF_EON: BRDF_EON,
	BRDF_GGX: BRDF_GGX,
	BRDF_Lambert: BRDF_Lambert,
	BRDF_Sheen: BRDF_Sheen,
	BasicPointShadowFilter: BasicPointShadowFilter,
	BasicShadowFilter: BasicShadowFilter,
	Break: Break,
	Const: Const,
	Continue: Continue,
	DFGLUT: DFGLUT,
	D_GGX: D_GGX,
	D_GGX_Anisotropic: D_GGX_Anisotropic,
	Discard: Discard,
	EON_DirectionalAlbedo: EON_DirectionalAlbedo,
	EPSILON: EPSILON,
	EnvironmentBRDF: EnvironmentBRDF,
	F_Schlick: F_Schlick,
	Fn: Fn,
	HALF_PI: HALF_PI,
	INFINITY: INFINITY,
	If: If,
	LTC_Evaluate: LTC_Evaluate,
	LTC_Evaluate_Volume: LTC_Evaluate_Volume,
	LTC_Uv: LTC_Uv,
	Loop: Loop,
	NodeAccess: NodeAccess,
	NodeShaderStage: NodeShaderStage,
	NodeType: NodeType,
	NodeUpdateType: NodeUpdateType,
	OnAfterObjectUpdate: OnAfterObjectUpdate,
	OnAfterRenderPipeline: OnAfterRenderPipeline,
	OnBeforeFrameUpdate: OnBeforeFrameUpdate,
	OnBeforeMaterialUpdate: OnBeforeMaterialUpdate,
	OnBeforeObjectUpdate: OnBeforeObjectUpdate,
	OnBeforeRenderPipeline: OnBeforeRenderPipeline,
	OnFrameUpdate: OnFrameUpdate,
	OnMaterialUpdate: OnMaterialUpdate,
	OnObjectUpdate: OnObjectUpdate,
	PCFShadowFilter: PCFShadowFilter,
	PI: PI,
	PI2: PI2,
	PointShadowFilter: PointShadowFilter,
	Return: Return,
	Schlick_to_F0: Schlick_to_F0,
	ShaderNode: ShaderNode,
	Stack: Stack,
	Switch: Switch,
	TBNViewMatrix: TBNViewMatrix,
	TWO_PI: TWO_PI,
	VSMShadowFilter: VSMShadowFilter,
	V_GGX_SmithCorrelated: V_GGX_SmithCorrelated,
	V_GGX_SmithCorrelated_Anisotropic: V_GGX_SmithCorrelated_Anisotropic,
	Var: Var,
	abs: abs,
	acesFilmicToneMapping: acesFilmicToneMapping,
	acos: acos,
	acosh: acosh,
	add: add,
	addMethodChaining: addMethodChaining,
	addNodeElement: addNodeElement,
	agxToneMapping: agxToneMapping,
	all: all,
	alphaT: alphaT,
	ambientOcclusion: ambientOcclusion,
	and: and,
	anisotropy: anisotropy,
	anisotropyB: anisotropyB,
	anisotropyT: anisotropyT,
	any: any,
	array: array,
	asin: asin,
	asinh: asinh,
	assign: assign,
	atan: atan,
	atanh: atanh,
	atomicAdd: atomicAdd,
	atomicAnd: atomicAnd,
	atomicFunc: atomicFunc,
	atomicLoad: atomicLoad,
	atomicMax: atomicMax,
	atomicMin: atomicMin,
	atomicOr: atomicOr,
	atomicStore: atomicStore,
	atomicSub: atomicSub,
	atomicXor: atomicXor,
	attenuationColor: attenuationColor,
	attenuationDistance: attenuationDistance,
	attribute: attribute,
	attributeArray: attributeArray,
	backgroundBlurriness: backgroundBlurriness,
	backgroundIntensity: backgroundIntensity,
	backgroundRotation: backgroundRotation,
	batch: batch,
	batchColor: batchColor,
	batchIndirectIndex: batchIndirectIndex,
	bentNormalView: bentNormalView,
	billboarding: billboarding,
	bitAnd: bitAnd,
	bitNot: bitNot,
	bitOr: bitOr,
	bitXor: bitXor,
	bitangentGeometry: bitangentGeometry,
	bitangentLocal: bitangentLocal,
	bitangentView: bitangentView,
	bitangentViewFrame: bitangentViewFrame,
	bitangentWorld: bitangentWorld,
	bitcast: bitcast,
	blendBurn: blendBurn,
	blendColor: blendColor,
	blendDodge: blendDodge,
	blendOverlay: blendOverlay,
	blendScreen: blendScreen,
	bool: bool,
	buffer: buffer,
	bufferAttribute: bufferAttribute,
	builtin: builtin,
	builtinAOContext: builtinAOContext,
	builtinGIContext: builtinGIContext,
	builtinShadowContext: builtinShadowContext,
	bumpMap: bumpMap,
	bvec2: bvec2,
	bvec3: bvec3,
	bvec4: bvec4,
	bypass: bypass,
	cache: cache,
	call: call,
	cameraFar: cameraFar,
	cameraIndex: cameraIndex,
	cameraNear: cameraNear,
	cameraNormalMatrix: cameraNormalMatrix,
	cameraPosition: cameraPosition,
	cameraProjectionMatrix: cameraProjectionMatrix,
	cameraProjectionMatrixInverse: cameraProjectionMatrixInverse,
	cameraViewMatrix: cameraViewMatrix,
	cameraViewport: cameraViewport,
	cameraWorldMatrix: cameraWorldMatrix,
	cbrt: cbrt,
	cdl: cdl,
	ceil: ceil,
	checker: checker,
	cineonToneMapping: cineonToneMapping,
	clamp: clamp,
	clearcoat: clearcoat,
	clearcoatNormalView: clearcoatNormalView,
	clearcoatRoughness: clearcoatRoughness,
	clipSpace: clipSpace,
	clipping: clipping,
	clippingAlpha: clippingAlpha,
	code: code,
	color: color,
	colorSpaceToWorking: colorSpaceToWorking,
	colorToDirection: colorToDirection,
	compute: compute,
	computeKernel: computeKernel,
	computeSkinning: computeSkinning,
	context: context,
	convert: convert,
	convertColorSpace: convertColorSpace,
	convertToTexture: convertToTexture,
	cos: cos,
	cosh: cosh,
	countLeadingZeros: countLeadingZeros,
	countOneBits: countOneBits,
	countTrailingZeros: countTrailingZeros,
	cross: cross,
	cubeTexture: cubeTexture,
	cubeTextureBase: cubeTextureBase,
	dFdx: dFdx,
	dFdy: dFdy,
	dashSize: dashSize,
	debug: debug,
	decrement: decrement,
	decrementBefore: decrementBefore,
	defaultBuildStages: defaultBuildStages,
	defaultShaderStages: defaultShaderStages,
	defined: defined,
	degrees: degrees,
	deltaTime: deltaTime,
	densityFogFactor: densityFogFactor,
	depth: depth,
	depthPass: depthPass,
	determinant: determinant,
	difference: difference,
	diffuseColor: diffuseColor,
	diffuseContribution: diffuseContribution,
	diffuseRoughness: diffuseRoughness,
	directPointLight: directPointLight,
	directionToColor: directionToColor,
	directionToFaceDirection: directionToFaceDirection,
	dispersion: dispersion,
	distance: distance,
	div: div,
	dot: dot,
	dot4I8Packed: dot4I8Packed,
	dot4U8Packed: dot4U8Packed,
	drawIndex: drawIndex,
	dynamicBufferAttribute: dynamicBufferAttribute,
	element: element,
	emissive: emissive,
	equal: equal,
	equirectDirection: equirectDirection,
	equirectUV: equirectUV,
	exp: exp,
	exp2: exp2,
	exponentialHeightFogFactor: exponentialHeightFogFactor,
	expression: expression,
	faceDirection: faceDirection,
	faceForward: faceForward,
	faceforward: faceforward,
	float: float,
	floatBitsToInt: floatBitsToInt,
	floatBitsToUint: floatBitsToUint,
	floor: floor,
	fog: fog,
	fract: fract,
	frameGroup: frameGroup,
	frameId: frameId,
	frontFacing: frontFacing,
	fwidth: fwidth,
	gain: gain,
	gapSize: gapSize,
	getConstNodeType: getConstNodeType,
	getCurrentStack: getCurrentStack,
	getDistanceAttenuation: getDistanceAttenuation,
	getGeometryRoughness: getGeometryRoughness,
	getNormalFromDepth: getNormalFromDepth,
	getParallaxCorrectNormal: getParallaxCorrectNormal,
	getRoughness: getRoughness,
	getScreenPosition: getScreenPosition,
	getScreenPositionFromClip: getScreenPositionFromClip,
	getShIrradianceAt: getShIrradianceAt,
	getTextureIndex: getTextureIndex,
	getViewPosition: getViewPosition,
	globalId: globalId,
	glsl: glsl,
	glslFn: glslFn,
	grayscale: grayscale,
	greaterThan: greaterThan,
	greaterThanEqual: greaterThanEqual,
	hardwareClipping: hardwareClipping,
	hash: hash,
	highpModelNormalViewMatrix: highpModelNormalViewMatrix,
	highpModelViewMatrix: highpModelViewMatrix,
	hue: hue,
	increment: increment,
	incrementBefore: incrementBefore,
	inspect: inspect,
	instance: instance,
	instanceColor: instanceColor,
	instanceIndex: instanceIndex,
	instancedArray: instancedArray,
	instancedBufferAttribute: instancedBufferAttribute,
	instancedDynamicBufferAttribute: instancedDynamicBufferAttribute,
	instancedMesh: instancedMesh,
	int: int,
	intBitsToFloat: intBitsToFloat,
	interleavedGradientNoise: interleavedGradientNoise,
	inverse: inverse,
	inverseSqrt: inverseSqrt,
	inversesqrt: inversesqrt,
	invocationLocalIndex: invocationLocalIndex,
	invocationSubgroupIndex: invocationSubgroupIndex,
	ior: ior,
	iridescence: iridescence,
	iridescenceIOR: iridescenceIOR,
	iridescenceThickness: iridescenceThickness,
	isBackgroundDepth: isBackgroundDepth,
	isolate: isolate,
	ivec2: ivec2,
	ivec3: ivec3,
	ivec4: ivec4,
	js: js,
	label: label,
	length: length,
	lengthSq: lengthSq,
	lessThan: lessThan,
	lessThanEqual: lessThanEqual,
	lightPosition: lightPosition,
	lightProjectionUV: lightProjectionUV,
	lightShadowMatrix: lightShadowMatrix,
	lightTargetDirection: lightTargetDirection,
	lightTargetPosition: lightTargetPosition,
	lightViewPosition: lightViewPosition,
	lightingContext: lightingContext,
	lights: lights,
	linearDepth: linearDepth,
	linearToneMapping: linearToneMapping,
	localId: localId,
	log: log,
	log2: log2,
	logarithmicDepthToViewZ: logarithmicDepthToViewZ,
	luminance: luminance,
	mat2: mat2,
	mat3: mat3,
	mat4: mat4,
	matcapUV: matcapUV,
	materialAO: materialAO,
	materialAlphaTest: materialAlphaTest,
	materialAnisotropy: materialAnisotropy,
	materialAnisotropyVector: materialAnisotropyVector,
	materialAttenuationColor: materialAttenuationColor,
	materialAttenuationDistance: materialAttenuationDistance,
	materialClearcoat: materialClearcoat,
	materialClearcoatNormal: materialClearcoatNormal,
	materialClearcoatRoughness: materialClearcoatRoughness,
	materialColor: materialColor,
	materialDiffuseRoughness: materialDiffuseRoughness,
	materialDispersion: materialDispersion,
	materialEmissive: materialEmissive,
	materialEnvIntensity: materialEnvIntensity,
	materialEnvRotation: materialEnvRotation,
	materialIOR: materialIOR,
	materialIridescence: materialIridescence,
	materialIridescenceIOR: materialIridescenceIOR,
	materialIridescenceThickness: materialIridescenceThickness,
	materialLightMap: materialLightMap,
	materialLineDashOffset: materialLineDashOffset,
	materialLineDashSize: materialLineDashSize,
	materialLineGapSize: materialLineGapSize,
	materialLineScale: materialLineScale,
	materialLineWidth: materialLineWidth,
	materialMetalness: materialMetalness,
	materialNormal: materialNormal,
	materialOpacity: materialOpacity,
	materialPointSize: materialPointSize,
	materialReference: materialReference,
	materialReflectivity: materialReflectivity,
	materialRefractionRatio: materialRefractionRatio,
	materialRetroreflectivity: materialRetroreflectivity,
	materialRotation: materialRotation,
	materialRoughness: materialRoughness,
	materialSheen: materialSheen,
	materialSheenRoughness: materialSheenRoughness,
	materialShininess: materialShininess,
	materialSpecular: materialSpecular,
	materialSpecularColor: materialSpecularColor,
	materialSpecularIntensity: materialSpecularIntensity,
	materialSpecularStrength: materialSpecularStrength,
	materialThickness: materialThickness,
	materialTransmission: materialTransmission,
	max: max,
	maxMipLevel: maxMipLevel,
	mediumpModelViewMatrix: mediumpModelViewMatrix,
	metalness: metalness,
	min: min,
	mix: mix,
	mixElement: mixElement,
	mod: mod,
	modelDirection: modelDirection,
	modelNormalMatrix: modelNormalMatrix,
	modelPosition: modelPosition,
	modelRadius: modelRadius,
	modelScale: modelScale,
	modelViewMatrix: modelViewMatrix,
	modelViewPosition: modelViewPosition,
	modelViewProjection: modelViewProjection,
	modelWorldMatrix: modelWorldMatrix,
	modelWorldMatrixInverse: modelWorldMatrixInverse,
	morphColor: morphColor,
	morphReference: morphReference,
	mrt: mrt,
	mul: mul,
	mx_aastep: mx_aastep,
	mx_add: mx_add,
	mx_atan2: mx_atan2,
	mx_cell_noise_float: mx_cell_noise_float,
	mx_cell_noise_vec3: mx_cell_noise_vec3,
	mx_contrast: mx_contrast,
	mx_divide: mx_divide,
	mx_fractal_noise_float: mx_fractal_noise_float,
	mx_fractal_noise_float_2d: mx_fractal_noise_float_2d,
	mx_fractal_noise_vec2: mx_fractal_noise_vec2,
	mx_fractal_noise_vec3: mx_fractal_noise_vec3,
	mx_fractal_noise_vec4: mx_fractal_noise_vec4,
	mx_frame: mx_frame,
	mx_heighttonormal: mx_heighttonormal,
	mx_hsvtorgb: mx_hsvtorgb,
	mx_ifequal: mx_ifequal,
	mx_ifgreater: mx_ifgreater,
	mx_ifgreatereq: mx_ifgreatereq,
	mx_invert: mx_invert,
	mx_modulo: mx_modulo,
	mx_multiply: mx_multiply,
	mx_noise_float: mx_noise_float,
	mx_noise_vec3: mx_noise_vec3,
	mx_noise_vec4: mx_noise_vec4,
	mx_place2d: mx_place2d,
	mx_power: mx_power,
	mx_ramp4: mx_ramp4,
	mx_ramplr: mx_ramplr,
	mx_ramptb: mx_ramptb,
	mx_rgbtohsv: mx_rgbtohsv,
	mx_rotate2d: mx_rotate2d,
	mx_rotate3d: mx_rotate3d,
	mx_safepower: mx_safepower,
	mx_separate: mx_separate,
	mx_smoothstep: mx_smoothstep,
	mx_splitlr: mx_splitlr,
	mx_splittb: mx_splittb,
	mx_srgb_texture_to_lin_rec709: mx_srgb_texture_to_lin_rec709,
	mx_subtract: mx_subtract,
	mx_timer: mx_timer,
	mx_transform_uv: mx_transform_uv,
	mx_unifiednoise2d: mx_unifiednoise2d,
	mx_unifiednoise3d: mx_unifiednoise3d,
	mx_worley_noise_float: mx_worley_noise_float,
	mx_worley_noise_float_2d: mx_worley_noise_float_2d,
	mx_worley_noise_float_3d: mx_worley_noise_float_3d,
	mx_worley_noise_vec2: mx_worley_noise_vec2,
	mx_worley_noise_vec3: mx_worley_noise_vec3,
	mx_worley_noise_vec3_style: mx_worley_noise_vec3_style,
	negate: negate,
	negateOnBackSide: negateOnBackSide,
	neutralToneMapping: neutralToneMapping,
	nodeArray: nodeArray,
	nodeImmutable: nodeImmutable,
	nodeObject: nodeObject,
	nodeObjectAssignable: nodeObjectAssignable,
	nodeObjects: nodeObjects,
	nodeProxy: nodeProxy,
	nodeProxyAssignable: nodeProxyAssignable,
	nodeProxyConstructor: nodeProxyConstructor,
	normalFlat: normalFlat,
	normalGeometry: normalGeometry,
	normalLocal: normalLocal,
	normalMap: normalMap,
	normalView: normalView,
	normalViewGeometry: normalViewGeometry,
	normalWorld: normalWorld,
	normalWorldGeometry: normalWorldGeometry,
	normalize: normalize,
	not: not,
	notEqual: notEqual,
	numWorkgroups: numWorkgroups,
	objectDirection: objectDirection,
	objectGroup: objectGroup,
	objectPosition: objectPosition,
	objectRadius: objectRadius,
	objectScale: objectScale,
	objectViewPosition: objectViewPosition,
	objectWorldMatrix: objectWorldMatrix,
	oneMinus: oneMinus,
	or: or,
	orthographicDepthToViewZ: orthographicDepthToViewZ,
	oscSawtooth: oscSawtooth,
	oscSine: oscSine,
	oscSquare: oscSquare,
	oscTriangle: oscTriangle,
	output: output,
	outputStruct: outputStruct,
	overloadingFn: overloadingFn,
	overrideNode: overrideNode,
	overrideNodes: overrideNodes,
	pack4xI8: pack4xI8,
	pack4xI8Clamp: pack4xI8Clamp,
	pack4xU8: pack4xU8,
	pack4xU8Clamp: pack4xU8Clamp,
	packHalf2x16: packHalf2x16,
	packNormalToRGB: packNormalToRGB,
	packSnorm2x16: packSnorm2x16,
	packSnorm4x8: packSnorm4x8,
	packUnorm2x16: packUnorm2x16,
	packUnorm4x8: packUnorm4x8,
	parabola: parabola,
	parallaxDirection: parallaxDirection,
	parallaxUV: parallaxUV,
	parameter: parameter,
	pass: pass,
	passTexture: passTexture,
	pcurve: pcurve,
	perspectiveDepthToViewZ: perspectiveDepthToViewZ,
	pmremTexture: pmremTexture,
	pointShadow: pointShadow,
	pointUV: pointUV,
	pointWidth: pointWidth,
	positionGeometry: positionGeometry,
	positionLocal: positionLocal,
	positionPrevious: positionPrevious,
	positionView: positionView,
	positionViewDirection: positionViewDirection,
	positionWorld: positionWorld,
	positionWorldDirection: positionWorldDirection,
	posterize: posterize,
	pow: pow,
	pow2: pow2,
	pow3: pow3,
	pow4: pow4,
	premultiplyAlpha: premultiplyAlpha,
	projectionDepth: projectionDepth,
	property: property,
	quadBroadcast: quadBroadcast,
	quadSwapDiagonal: quadSwapDiagonal,
	quadSwapX: quadSwapX,
	quadSwapY: quadSwapY,
	radians: radians,
	rand: rand,
	range: range,
	rangeFogFactor: rangeFogFactor,
	reciprocal: reciprocal,
	reference: reference,
	referenceAttribute: referenceAttribute,
	referenceBuffer: referenceBuffer,
	reflect: reflect,
	reflectVector: reflectVector,
	reflectView: reflectView,
	reflector: reflector,
	refract: refract,
	refractVector: refractVector,
	refractView: refractView,
	reinhardToneMapping: reinhardToneMapping,
	remap: remap,
	remapClamp: remapClamp,
	renderGroup: renderGroup,
	renderOutput: renderOutput,
	rendererReference: rendererReference,
	replaceDefaultUV: replaceDefaultUV,
	retroreflectivity: retroreflectivity,
	rotate: rotate,
	rotateUV: rotateUV,
	roughness: roughness,
	round: round,
	rtt: rtt,
	sRGBTransferEOTF: sRGBTransferEOTF,
	sRGBTransferOETF: sRGBTransferOETF,
	sample: sample,
	sampler: sampler,
	samplerComparison: samplerComparison,
	saturate: saturate,
	saturation: saturation,
	screenCoordinate: screenCoordinate,
	screenDPR: screenDPR,
	screenSize: screenSize,
	screenUV: screenUV,
	select: select,
	setCurrentStack: setCurrentStack,
	setName: setName,
	shaderStages: shaderStages,
	shadow: shadow,
	shadowPositionWorld: shadowPositionWorld,
	shapeCircle: shapeCircle,
	sharedUniformGroup: sharedUniformGroup,
	sheen: sheen,
	sheenRoughness: sheenRoughness,
	shiftLeft: shiftLeft,
	shiftRight: shiftRight,
	shininess: shininess,
	sign: sign,
	sin: sin,
	sinc: sinc,
	sinh: sinh,
	skinning: skinning,
	smoothstep: smoothstep,
	smoothstepElement: smoothstepElement,
	specularColor: specularColor,
	specularColorBlended: specularColorBlended,
	specularF90: specularF90,
	spherizeUV: spherizeUV,
	split: split,
	spritesheetUV: spritesheetUV,
	sqrt: sqrt,
	stack: stack,
	step: step,
	stepElement: stepElement,
	storage: storage,
	storageBarrier: storageBarrier,
	storageElement: storageElement,
	storageTexture: storageTexture,
	storageTexture3D: storageTexture3D,
	struct: struct,
	sub: sub,
	subBuild: subBuild,
	subgroupAdd: subgroupAdd,
	subgroupAll: subgroupAll,
	subgroupAnd: subgroupAnd,
	subgroupAny: subgroupAny,
	subgroupBallot: subgroupBallot,
	subgroupBroadcast: subgroupBroadcast,
	subgroupBroadcastFirst: subgroupBroadcastFirst,
	subgroupElect: subgroupElect,
	subgroupExclusiveAdd: subgroupExclusiveAdd,
	subgroupExclusiveMul: subgroupExclusiveMul,
	subgroupInclusiveAdd: subgroupInclusiveAdd,
	subgroupInclusiveMul: subgroupInclusiveMul,
	subgroupIndex: subgroupIndex,
	subgroupMax: subgroupMax,
	subgroupMin: subgroupMin,
	subgroupMul: subgroupMul,
	subgroupOr: subgroupOr,
	subgroupShuffle: subgroupShuffle,
	subgroupShuffleDown: subgroupShuffleDown,
	subgroupShuffleUp: subgroupShuffleUp,
	subgroupShuffleXor: subgroupShuffleXor,
	subgroupSize: subgroupSize,
	subgroupXor: subgroupXor,
	tan: tan,
	tangentGeometry: tangentGeometry,
	tangentLocal: tangentLocal,
	tangentView: tangentView,
	tangentViewFrame: tangentViewFrame,
	tangentWorld: tangentWorld,
	tanh: tanh,
	texture: texture,
	texture3D: texture3D,
	texture3DLevel: texture3DLevel,
	texture3DLoad: texture3DLoad,
	textureBarrier: textureBarrier,
	textureBicubic: textureBicubic,
	textureBicubicLevel: textureBicubicLevel,
	textureLevel: textureLevel,
	textureLoad: textureLoad,
	textureSize: textureSize,
	textureStore: textureStore,
	thickness: thickness,
	time: time,
	toneMapping: toneMapping,
	toneMappingExposure: toneMappingExposure,
	toonOutlinePass: toonOutlinePass,
	transformDirection: transformDirection,
	transformNormal: transformNormal,
	transformNormalByInverseViewMatrix: transformNormalByInverseViewMatrix,
	transformNormalByViewMatrix: transformNormalByViewMatrix,
	transformNormalToView: transformNormalToView,
	transmission: transmission,
	transpose: transpose,
	triNoise3D: triNoise3D,
	triplanarTexture: triplanarTexture,
	triplanarTextures: triplanarTextures,
	trunc: trunc,
	uint: uint,
	uintBitsToFloat: uintBitsToFloat,
	uniform: uniform,
	uniformArray: uniformArray,
	uniformCubeTexture: uniformCubeTexture,
	uniformFlow: uniformFlow,
	uniformGroup: uniformGroup,
	uniformTexture: uniformTexture,
	unpack4xI8: unpack4xI8,
	unpack4xU8: unpack4xU8,
	unpackHalf2x16: unpackHalf2x16,
	unpackNormal: unpackNormal,
	unpackRGBToNormal: unpackRGBToNormal,
	unpackSnorm2x16: unpackSnorm2x16,
	unpackSnorm4x8: unpackSnorm4x8,
	unpackUnorm2x16: unpackUnorm2x16,
	unpackUnorm4x8: unpackUnorm4x8,
	unpremultiplyAlpha: unpremultiplyAlpha,
	userData: userData,
	uv: uv$1,
	uvec2: uvec2,
	uvec3: uvec3,
	uvec4: uvec4,
	varying: varying,
	varyingProperty: varyingProperty,
	vec2: vec2,
	vec3: vec3,
	vec4: vec4,
	vectorComponents: vectorComponents,
	velocity: velocity,
	vertexColor: vertexColor,
	vertexIndex: vertexIndex,
	vertexStage: vertexStage,
	vibrance: vibrance,
	viewZToLogarithmicDepth: viewZToLogarithmicDepth,
	viewZToOrthographicDepth: viewZToOrthographicDepth,
	viewZToPerspectiveDepth: viewZToPerspectiveDepth,
	viewZToReversedOrthographicDepth: viewZToReversedOrthographicDepth,
	viewZToReversedPerspectiveDepth: viewZToReversedPerspectiveDepth,
	viewport: viewport,
	viewportCoordinate: viewportCoordinate,
	viewportDepthTexture: viewportDepthTexture,
	viewportLinearDepth: viewportLinearDepth,
	viewportMipTexture: viewportMipTexture,
	viewportOpaqueMipTexture: viewportOpaqueMipTexture,
	viewportSafeUV: viewportSafeUV,
	viewportSharedTexture: viewportSharedTexture,
	viewportSize: viewportSize,
	viewportTexture: viewportTexture,
	viewportUV: viewportUV,
	vogelDiskSample: vogelDiskSample,
	wgsl: wgsl,
	wgslFn: wgslFn,
	workgroupArray: workgroupArray,
	workgroupBarrier: workgroupBarrier,
	workgroupId: workgroupId,
	workingLuminanceCoefficients: workingLuminanceCoefficients,
	workingToColorSpace: workingToColorSpace,
	xor: xor
});

export { AONode, AnalyticLightNode, ArrayElementNode, ArrayNode, AssignNode, AtomicFunctionNode, AttributeNode, BRDF_EON, BRDF_GGX, BRDF_Lambert, BRDF_Sheen, BarrierNode, BasicPointShadowFilter, BasicShadowFilter, BitcastNode, BitcountNode, BlendMode, Break, BufferAttributeNode, BufferNode, BuiltinNode, BumpMapNode, BypassNode, ChainMap, ClippingNode, CodeNode, Color4, ColorSpaceNode, ComputeBuiltinNode, ComputeNode, ConditionalNode, Const, ConstNode, ContextNode, Continue, ConvertNode, CubeRenderTarget, CubeTextureNode, DFGLUT, D_GGX, D_GGX_Anisotropic, DebugNode, Discard, EON_DirectionalAlbedo, EPSILON, EnvironmentBRDF, EventNode, ExpressionNode, F_Schlick, FlipNode, Fn, FrontFacingNode, FunctionCallNode, FunctionNode, FunctionOverloadingNode, HALF_PI, INFINITY, If, IndexNode, InputNode, InspectorBase, InspectorNode, IrradianceNode, IsolateNode, JoinNode, LTC_Evaluate, LTC_Evaluate_Volume, LTC_Uv, LightingContextNode, LightingNode, LightsNode, Loop, LoopNode, MRTNode, MaterialNode, MaterialReferenceNode, MathNode, MaxMipLevelNode, MemberNode, ModelNode, Node, NodeAccess, NodeError, NodeMaterial, NodeMaterialObserver, NodeShaderStage, NodeType, NodeUpdateType, NodeUtils, NormalMapNode, Object3DNode, OnAfterObjectUpdate, OnAfterRenderPipeline, OnBeforeFrameUpdate, OnBeforeMaterialUpdate, OnBeforeObjectUpdate, OnBeforeRenderPipeline, OnFrameUpdate, OnMaterialUpdate, OnObjectUpdate, OperatorNode, OutputStructNode, OverrideContextNode, PCFShadowFilter, PI, PI2, PMREMGenerator, PMREMNode, PackFloatNode, Packed4x8IntegerNode, ParameterNode, PassNode, PointLightNode, PointShadowFilter, PointShadowNode, PointUVNode, PropertyNode, QuadMesh, RTTNode, RangeNode, ReferenceAttributeNode, ReferenceBaseNode, ReferenceElementNode, ReferenceNode, ReflectorNode, RenderOutputNode, RendererReferenceNode, RendererUtils, Return, RotateNode, SampleNode, Schlick_to_F0, ScreenNode, SetNode, ShaderNode, ShadowBaseNode, ShadowNode, SplitNode, Stack, StackNode, StackTrace, StorageArrayElementNode, StorageBufferAttribute, StorageBufferNode, StorageInstancedBufferAttribute, StorageTexture3DNode, StorageTextureNode, StructNode, StructTypeNode, SubBuildNode, SubgroupFunctionNode, Switch, TBNViewMatrix, TWO_PI, Texture3DNode, TextureNode, TextureSizeNode, Three_TSL, ToneMappingNode, ToonOutlinePassNode, UniformArrayNode, UniformGroupNode, UniformNode, UnpackFloatNode, UserDataNode, VSMShadowFilter, V_GGX_SmithCorrelated, V_GGX_SmithCorrelated_Anisotropic, Var, VarNode, VaryingNode, VelocityNode, VertexColorNode, ViewportDepthNode, ViewportDepthTextureNode, ViewportSharedTextureNode, ViewportTextureNode, WorkgroupInfoNode, abs, acesFilmicToneMapping, acos, acosh, add, addMethodChaining, addNodeElement, agxToneMapping, all, alphaT, ambientOcclusion, and, anisotropy, anisotropyB, anisotropyT, any, array, asin, asinh, assign, atan, atanh, atomicAdd, atomicAnd, atomicFunc, atomicLoad, atomicMax, atomicMin, atomicOr, atomicStore, atomicSub, atomicXor, attenuationColor, attenuationDistance, attribute, attributeArray, backgroundBlurriness, backgroundIntensity, backgroundRotation, batch, batchColor, batchIndirectIndex, bentNormalView, billboarding, bitAnd, bitNot, bitOr, bitXor, bitangentGeometry, bitangentLocal, bitangentView, bitangentViewFrame, bitangentWorld, bitcast, blendBurn, blendColor, blendDodge, blendOverlay, blendScreen, bool, buffer, bufferAttribute, builtin, builtinAOContext, builtinGIContext, builtinShadowContext, bumpMap, bvec2, bvec3, bvec4, bypass, cache, call, cameraFar, cameraIndex, cameraNear, cameraNormalMatrix, cameraPosition, cameraProjectionMatrix, cameraProjectionMatrixInverse, cameraViewMatrix, cameraViewport, cameraWorldMatrix, cbrt, cdl, ceil, checker, cineonToneMapping, clamp, clearcoat, clearcoatNormalView, clearcoatRoughness, clipSpace, clipping, clippingAlpha, code, color, colorSpaceToWorking, colorToDirection, compute, computeKernel, computeSkinning, context, convert, convertColorSpace, convertToTexture, cos, cosh, countLeadingZeros, countOneBits, countTrailingZeros, cross, cubeTexture, cubeTextureBase, dFdx, dFdy, dashSize, debug, decrement, decrementBefore, defaultBuildStages, defaultShaderStages, defined, degrees, deltaTime, densityFogFactor, depth, depthPass, determinant, difference, diffuseColor, diffuseContribution, diffuseRoughness, directPointLight, directionToColor, directionToFaceDirection, dispersion, distance, div, dot, dot4I8Packed, dot4U8Packed, drawIndex, dynamicBufferAttribute, element, emissive, equal, equirectDirection, equirectUV, exp, exp2, exponentialHeightFogFactor, expression, faceDirection, faceForward, faceforward, float, floatBitsToInt, floatBitsToUint, floor, fog, fract, frameGroup, frameId, frontFacing, fwidth, gain, gapSize, getConstNodeType, getCurrentStack, getDistanceAttenuation, getGeometryRoughness, getNormalFromDepth, getParallaxCorrectNormal, getRoughness, getScreenPosition, getScreenPositionFromClip, getShIrradianceAt, getTextureIndex, getTextureType, getTypeFromLength, getViewPosition, globalId, glsl, glslFn, grayscale, greaterThan, greaterThanEqual, hardwareClipping, hash, hashArray, hashString, highpModelNormalViewMatrix, highpModelViewMatrix, hue, increment, incrementBefore, inspect, instance, instanceColor, instanceIndex, instancedArray, instancedBufferAttribute, instancedDynamicBufferAttribute, instancedMesh, int, intBitsToFloat, interleavedGradientNoise, inverse, inverseSqrt, inversesqrt, invocationLocalIndex, invocationSubgroupIndex, ior, iridescence, iridescenceIOR, iridescenceThickness, isArrayAsParameter, isBackgroundDepth, isolate, ivec2, ivec3, ivec4, js, label, length, lengthSq, lessThan, lessThanEqual, lightPosition, lightProjectionUV, lightShadowMatrix, lightTargetDirection, lightTargetPosition, lightViewPosition, lightingContext, lights, linearDepth, linearToneMapping, localId, log, log2, logarithmicDepthToViewZ, luminance, mat2, mat3, mat4, matcapUV, materialAO, materialAlphaTest, materialAnisotropy, materialAnisotropyVector, materialAttenuationColor, materialAttenuationDistance, materialClearcoat, materialClearcoatNormal, materialClearcoatRoughness, materialColor, materialDiffuseRoughness, materialDispersion, materialEmissive, materialEnvIntensity, materialEnvRotation, materialIOR, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLightMap, materialLineDashOffset, materialLineDashSize, materialLineGapSize, materialLineScale, materialLineWidth, materialMetalness, materialNormal, materialOpacity, materialPointSize, materialReference, materialReflectivity, materialRefractionRatio, materialRetroreflectivity, materialRotation, materialRoughness, materialSheen, materialSheenRoughness, materialShininess, materialSpecular, materialSpecularColor, materialSpecularIntensity, materialSpecularStrength, materialThickness, materialTransmission, max, maxMipLevel, mediumpModelViewMatrix, mergeContext, metalness, min, mix, mixElement, mod, modelDirection, modelNormalMatrix, modelPosition, modelRadius, modelScale, modelViewMatrix, modelViewPosition, modelViewProjection, modelWorldMatrix, modelWorldMatrixInverse, morphColor, morphReference, mrt, mul, mx_aastep, mx_add, mx_atan2, mx_cell_noise_float, mx_cell_noise_vec3, mx_contrast, mx_divide, mx_fractal_noise_float, mx_fractal_noise_float_2d, mx_fractal_noise_vec2, mx_fractal_noise_vec3, mx_fractal_noise_vec4, mx_frame, mx_heighttonormal, mx_hsvtorgb, mx_ifequal, mx_ifgreater, mx_ifgreatereq, mx_invert, mx_modulo, mx_multiply, mx_noise_float, mx_noise_vec3, mx_noise_vec4, mx_place2d, mx_power, mx_ramp4, mx_ramplr, mx_ramptb, mx_rgbtohsv, mx_rotate2d, mx_rotate3d, mx_safepower, mx_separate, mx_smoothstep, mx_splitlr, mx_splittb, mx_srgb_texture_to_lin_rec709, mx_subtract, mx_timer, mx_transform_uv, mx_unifiednoise2d, mx_unifiednoise3d, mx_worley_noise_float, mx_worley_noise_float_2d, mx_worley_noise_float_3d, mx_worley_noise_vec2, mx_worley_noise_vec3, mx_worley_noise_vec3_style, negate, negateOnBackSide, neutralToneMapping, nodeArray, nodeImmutable, nodeObject, nodeObjectAssignable, nodeObjects, nodeProxy, nodeProxyAssignable, nodeProxyConstructor, normalFlat, normalGeometry, normalLocal, normalMap, normalView, normalViewGeometry, normalWorld, normalWorldGeometry, normalize, not, notEqual, numWorkgroups, objectDirection, objectGroup, objectPosition, objectRadius, objectScale, objectViewPosition, objectWorldMatrix, oneMinus, or, orthographicDepthToViewZ, oscSawtooth, oscSine, oscSquare, oscTriangle, output, outputStruct, overloadingFn, overrideNode, overrideNodes, pack4xI8, pack4xI8Clamp, pack4xU8, pack4xU8Clamp, packHalf2x16, packNormalToRGB, packSnorm2x16, packSnorm4x8, packUnorm2x16, packUnorm4x8, parabola, parallaxDirection, parallaxUV, parameter, pass, passTexture, pcurve, perspectiveDepthToViewZ, pmremTexture, pointShadow, pointUV, pointWidth, positionGeometry, positionLocal, positionPrevious, positionView, positionViewDirection, positionWorld, positionWorldDirection, posterize, pow, pow2, pow3, pow4, premultiplyAlpha, projectionDepth, property, quadBroadcast, quadSwapDiagonal, quadSwapX, quadSwapY, radians, rand, range, rangeFogFactor, reciprocal, reference, reference$1, referenceAttribute, referenceBuffer, reflect, reflectVector, reflectView, reflector, refract, refractVector, refractView, reinhardToneMapping, remap, remapClamp, renderGroup, renderOutput, rendererReference, replaceDefaultUV, retroreflectivity, rotate, rotateUV, roughness, round, rtt, sRGBTransferEOTF, sRGBTransferOETF, sample, sampler, samplerComparison, saturate, saturation, screenCoordinate, screenDPR, screenSize, screenUV, select, setCurrentStack, setName, shaderStages, shadow, shadowPositionWorld, shapeCircle, sharedUniformGroup, sheen, sheenRoughness, shiftLeft, shiftRight, shininess, sign, sin, sinc, sinh, skinning, smoothstep, smoothstepElement, specularColor, specularColorBlended, specularF90, spherizeUV, split, spritesheetUV, sqrt, stack, step, stepElement, storage, storageBarrier, storageElement, storageTexture, storageTexture3D, struct, sub, subBuild, subgroupAdd, subgroupAll, subgroupAnd, subgroupAny, subgroupBallot, subgroupBroadcast, subgroupBroadcastFirst, subgroupElect, subgroupExclusiveAdd, subgroupExclusiveMul, subgroupInclusiveAdd, subgroupInclusiveMul, subgroupIndex, subgroupMax, subgroupMin, subgroupMul, subgroupOr, subgroupShuffle, subgroupShuffleDown, subgroupShuffleUp, subgroupShuffleXor, subgroupSize, subgroupXor, tan, tangentGeometry, tangentLocal, tangentView, tangentViewFrame, tangentWorld, tanh, texture, texture3D, texture3DLevel, texture3DLoad, textureBarrier, textureBicubic, textureBicubicLevel, textureLevel, textureLoad, textureSize, textureStore, thickness, time, toneMapping, toneMappingExposure, toonOutlinePass, transformDirection, transformNormal, transformNormalByInverseViewMatrix, transformNormalByViewMatrix, transformNormalToView, transmission, transpose, triNoise3D, triplanarTexture, triplanarTextures, trunc, uint, uintBitsToFloat, uniform, uniformArray, uniformCubeTexture, uniformFlow, uniformGroup, uniformTexture, unpack4xI8, unpack4xU8, unpackHalf2x16, unpackNormal, unpackRGBToNormal, unpackSnorm2x16, unpackSnorm4x8, unpackUnorm2x16, unpackUnorm4x8, unpremultiplyAlpha, userData, uv$1 as uv, uvec2, uvec3, uvec4, varying, varyingProperty, vec2, vec3, vec4, vectorComponents, velocity, vertexColor, vertexIndex, vertexStage, vibrance, viewZToLogarithmicDepth, viewZToOrthographicDepth, viewZToPerspectiveDepth, viewZToReversedOrthographicDepth, viewZToReversedPerspectiveDepth, viewport, viewportCoordinate, viewportDepthTexture, viewportLinearDepth, viewportMipTexture, viewportOpaqueMipTexture, viewportSafeUV, viewportSharedTexture, viewportSize, viewportTexture, viewportUV, vogelDiskSample, wgsl, wgslFn, workgroupArray, workgroupBarrier, workgroupId, workingLuminanceCoefficients, workingToColorSpace, xor };