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