Accueil / npm / three@0.186.1 / examples / jsm / tsl / lighting / DynamicLightsNode.js

DynamicLightsNode.js

import { LightsNode, NodeUtils, warn } from 'three/webgpu';
import { nodeObject } from 'three/tsl';

import AmbientLightDataNode from './data/AmbientLightDataNode.js';
import DirectionalLightDataNode from './data/DirectionalLightDataNode.js';
import PointLightDataNode from './data/PointLightDataNode.js';
import SpotLightDataNode from './data/SpotLightDataNode.js';
import HemisphereLightDataNode from './data/HemisphereLightDataNode.js';

const _lightNodeRef = /*@__PURE__*/ new WeakMap();
const _hashData = [];

const _lightTypeToDataNode = {
	AmbientLight: AmbientLightDataNode,
	DirectionalLight: DirectionalLightDataNode,
	PointLight: PointLightDataNode,
	SpotLight: SpotLightDataNode,
	HemisphereLight: HemisphereLightDataNode
};

const _lightTypeToMaxProp = {
	DirectionalLight: 'maxDirectionalLights',
	PointLight: 'maxPointLights',
	SpotLight: 'maxSpotLights',
	HemisphereLight: 'maxHemisphereLights'
};

const sortLights = ( lights ) => lights.sort( ( a, b ) => a.id - b.id );

const isSpecialSpotLight = ( light ) => {

	return light.isSpotLight === true && ( light.map !== null || light.colorNode !== undefined );

};

const canBatchLight = ( light ) => {

	return light.isNode !== true &&
		light.castShadow !== true &&
		isSpecialSpotLight( light ) === false &&
		_lightTypeToDataNode[ light.constructor.name ] !== undefined;

};

const getOrCreateLightNode = ( light, nodeLibrary ) => {

	const lightNodeClass = nodeLibrary.getLightNodeClass( light.constructor );

	if ( lightNodeClass === null ) {

		warn( `DynamicLightsNode: Light node not found for ${ light.constructor.name }.` );
		return null;

	}

	if ( _lightNodeRef.has( light ) === false ) {

		_lightNodeRef.set( light, new lightNodeClass( light ) );

	}

	return _lightNodeRef.get( light );

};

/**
 * A custom version of `LightsNode` that batches supported analytic lights into
 * uniform arrays and loops.
 *
 * Unsupported lights, node lights, shadow-casting lights, and projected spot
 * lights keep the default per-light path.
 *
 * @augments LightsNode
 * @three_import import { DynamicLightsNode } from 'three/addons/tsl/lighting/DynamicLightsNode.js';
 */
class DynamicLightsNode extends LightsNode {

	static get type() {

		return 'DynamicLightsNode';

	}

	/**
	 * Constructs a new dynamic lights node.
	 *
	 * @param {Object} [options={}] - Dynamic lighting configuration.
	 * @param {number} [options.maxDirectionalLights=8] - Maximum number of batched directional lights.
	 * @param {number} [options.maxPointLights=16] - Maximum number of batched point lights.
	 * @param {number} [options.maxSpotLights=16] - Maximum number of batched spot lights.
	 * @param {number} [options.maxHemisphereLights=4] - Maximum number of batched hemisphere lights.
	 */
	constructor( options = {} ) {

		super();

		this.maxDirectionalLights = options.maxDirectionalLights !== undefined ? options.maxDirectionalLights : 8;
		this.maxPointLights = options.maxPointLights !== undefined ? options.maxPointLights : 16;
		this.maxSpotLights = options.maxSpotLights !== undefined ? options.maxSpotLights : 16;
		this.maxHemisphereLights = options.maxHemisphereLights !== undefined ? options.maxHemisphereLights : 4;

		this._dataNodes = new Map();

	}

	customCacheKey() {

		const typeSet = new Set();

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

			const light = this._lights[ i ];

			if ( canBatchLight( light ) ) {

				typeSet.add( light.constructor.name );

			} else {

				_hashData.push( light.id );
				_hashData.push( light.castShadow ? 1 : 0 );

				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 );

				}

			}

		}

		for ( const typeName of this._dataNodes.keys() ) {

			typeSet.add( typeName );

		}

		for ( const typeName of [ ...typeSet ].sort() ) {

			_hashData.push( NodeUtils.hashString( typeName ) );

		}

		const cacheKey = NodeUtils.hashArray( _hashData );

		_hashData.length = 0;

		return cacheKey;

	}

	setupLightsNode( builder ) {

		const lightNodes = [];
		const lightsByType = new Map();
		const lights = sortLights( this._lights );
		const nodeLibrary = builder.renderer.library;

		for ( const light of lights ) {

			if ( light.isNode === true ) {

				lightNodes.push( nodeObject( light ) );
				continue;

			}

			if ( canBatchLight( light ) ) {

				const typeName = light.constructor.name;
				const typeLights = lightsByType.get( typeName );

				if ( typeLights === undefined ) {

					lightsByType.set( typeName, [ light ] );

				} else {

					typeLights.push( light );

				}

				continue;

			}

			const lightNode = getOrCreateLightNode( light, nodeLibrary );

			if ( lightNode !== null ) {

				lightNodes.push( lightNode );

			}

		}

		for ( const [ typeName, typeLights ] of lightsByType ) {

			let dataNode = this._dataNodes.get( typeName );

			if ( dataNode === undefined ) {

				const DataNodeClass = _lightTypeToDataNode[ typeName ];
				const maxProp = _lightTypeToMaxProp[ typeName ];
				const maxCount = maxProp !== undefined ? this[ maxProp ] : undefined;

				dataNode = maxCount !== undefined ? new DataNodeClass( maxCount ) : new DataNodeClass();

				this._dataNodes.set( typeName, dataNode );

			}

			dataNode.setLights( typeLights );
			lightNodes.push( dataNode );

		}

		for ( const [ typeName, dataNode ] of this._dataNodes ) {

			if ( lightsByType.has( typeName ) === false ) {

				dataNode.setLights( [] );
				lightNodes.push( dataNode );

			}

		}

		return lightNodes;

	}

	setLights( lights ) {

		super.setLights( lights );

		if ( this._dataNodes.size > 0 ) {

			this._updateDataNodeLights( lights );

		}

		return this;

	}

	_updateDataNodeLights( lights ) {

		const lightsByType = new Map();

		for ( const light of lights ) {

			if ( canBatchLight( light ) === false ) continue;

			const typeName = light.constructor.name;
			const typeLights = lightsByType.get( typeName );

			if ( typeLights === undefined ) {

				lightsByType.set( typeName, [ light ] );

			} else {

				typeLights.push( light );

			}

		}

		for ( const [ typeName, dataNode ] of this._dataNodes ) {

			dataNode.setLights( lightsByType.get( typeName ) || [] );

		}

	}

	get hasLights() {

		return super.hasLights || this._dataNodes.size > 0;

	}

}

export default DynamicLightsNode;

/**
 * TSL function that creates a dynamic lights node.
 *
 * @tsl
 * @function
 * @param {Object} [options={}] - Dynamic lighting configuration.
 * @return {DynamicLightsNode} The created dynamic lights node.
 */
export const dynamicLights = ( options = {} ) => new DynamicLightsNode( options );