199 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			199 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import Node from '../core/Node.js';
 | |
| import AnalyticLightNode from './AnalyticLightNode.js';
 | |
| import { nodeObject, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
 | |
| 
 | |
| const LightNodes = new WeakMap();
 | |
| 
 | |
| const sortLights = ( lights ) => {
 | |
| 
 | |
| 	return lights.sort( ( a, b ) => a.id - b.id );
 | |
| 
 | |
| };
 | |
| 
 | |
| class LightsNode extends Node {
 | |
| 
 | |
| 	constructor( lightNodes = [] ) {
 | |
| 
 | |
| 		super( 'vec3' );
 | |
| 
 | |
| 		this.totalDiffuseNode = vec3().temp( 'totalDiffuse' );
 | |
| 		this.totalSpecularNode = vec3().temp( 'totalSpecular' );
 | |
| 
 | |
| 		this.outgoingLightNode = vec3().temp( 'outgoingLight' );
 | |
| 
 | |
| 		this.lightNodes = lightNodes;
 | |
| 
 | |
| 		this._hash = null;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	get hasLight() {
 | |
| 
 | |
| 		return this.lightNodes.length > 0;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	getHash() {
 | |
| 
 | |
| 		if ( this._hash === null ) {
 | |
| 
 | |
| 			const hash = [];
 | |
| 
 | |
| 			for ( const lightNode of this.lightNodes ) {
 | |
| 
 | |
| 				hash.push( lightNode.getHash() );
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 			this._hash = 'lights-' + hash.join( ',' );
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		return this._hash;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	setup( builder ) {
 | |
| 
 | |
| 		const context = builder.context;
 | |
| 		const lightingModel = context.lightingModel;
 | |
| 
 | |
| 		let outgoingLightNode = this.outgoingLightNode;
 | |
| 
 | |
| 		if ( lightingModel ) {
 | |
| 
 | |
| 			const { lightNodes, totalDiffuseNode, totalSpecularNode } = this;
 | |
| 
 | |
| 			context.outgoingLight = outgoingLightNode;
 | |
| 
 | |
| 			const stack = builder.addStack();
 | |
| 
 | |
| 			//
 | |
| 
 | |
| 			lightingModel.start( context, stack, builder );
 | |
| 
 | |
| 			// lights
 | |
| 
 | |
| 			for ( const lightNode of lightNodes ) {
 | |
| 
 | |
| 				lightNode.build( builder );
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 			//
 | |
| 
 | |
| 			lightingModel.indirectDiffuse( context, stack, builder );
 | |
| 			lightingModel.indirectSpecular( context, stack, builder );
 | |
| 			lightingModel.ambientOcclusion( context, stack, 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 );
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				context.material.transparent = true;
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 			totalDiffuseNode.assign( totalDiffuse );
 | |
| 			totalSpecularNode.assign( directSpecular.add( indirectSpecular ) );
 | |
| 
 | |
| 			outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) );
 | |
| 
 | |
| 			//
 | |
| 
 | |
| 			lightingModel.finish( context, stack, builder );
 | |
| 
 | |
| 			//
 | |
| 
 | |
| 			outgoingLightNode = outgoingLightNode.bypass( builder.removeStack() );
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		return outgoingLightNode;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	_getLightNodeById( id ) {
 | |
| 
 | |
| 		for ( const lightNode of this.lightNodes ) {
 | |
| 
 | |
| 			if ( lightNode.isAnalyticLightNode && lightNode.light.id === id ) {
 | |
| 
 | |
| 				return lightNode;
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		return null;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	fromLights( lights = [] ) {
 | |
| 
 | |
| 		const lightNodes = [];
 | |
| 
 | |
| 		lights = sortLights( lights );
 | |
| 
 | |
| 		for ( const light of lights ) {
 | |
| 
 | |
| 			let lightNode = this._getLightNodeById( light.id );
 | |
| 
 | |
| 			if ( lightNode === null ) {
 | |
| 
 | |
| 				const lightClass = light.constructor;
 | |
| 				const lightNodeClass = LightNodes.has( lightClass ) ? LightNodes.get( lightClass ) : AnalyticLightNode;
 | |
| 
 | |
| 				lightNode = nodeObject( new lightNodeClass( light ) );
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 			lightNodes.push( lightNode );
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		this.lightNodes = lightNodes;
 | |
| 		this._hash = null;
 | |
| 
 | |
| 		return this;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| export default LightsNode;
 | |
| 
 | |
| export const lights = ( lights ) => nodeObject( new LightsNode().fromLights( lights ) );
 | |
| export const lightsNode = nodeProxy( LightsNode );
 | |
| 
 | |
| export function addLightNode( lightClass, lightNodeClass ) {
 | |
| 
 | |
| 	if ( LightNodes.has( lightClass ) ) {
 | |
| 
 | |
| 		console.warn( `Redefinition of light node ${ lightNodeClass.type }` );
 | |
| 		return;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	if ( typeof lightClass !== 'function' ) throw new Error( `Light ${ lightClass.name } is not a class` );
 | |
| 	if ( typeof lightNodeClass !== 'function' || ! lightNodeClass.type ) throw new Error( `Light node ${ lightNodeClass.type } is not a class` );
 | |
| 
 | |
| 	LightNodes.set( lightClass, lightNodeClass );
 | |
| 
 | |
| }
 |