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