UNPKG

three

Version:

JavaScript 3D library

447 lines (291 loc) 8.6 kB
import Node from '../core/Node.js'; import { nodeObject, vec3 } from '../tsl/TSLBase.js'; import { hashArray } from '../core/NodeUtils.js'; const sortLights = ( lights ) => { return lights.sort( ( a, b ) => a.id - b.id ); }; const getLightNodeById = ( id, lightNodes ) => { for ( const lightNode of lightNodes ) { if ( lightNode.isAnalyticLightNode && lightNode.light.id === id ) { return lightNode; } } return null; }; const _lightsNodeRef = /*@__PURE__*/ new WeakMap(); /** * 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 = vec3().toVar(); /** * A node representing the total specular light. * * @type {Node<vec3>} */ this.totalSpecularNode = vec3().toVar(); /** * A node representing the outgoing light. * * @type {Node<vec3>} */ this.outgoingLightNode = vec3().toVar(); /** * An array representing the lights in the scene. * * @private * @type {Array<Light>} */ this._lights = []; /** * For each light in the scene, this node will create a * corresponding light node. * * @private * @type {?Array<LightingNode>} * @default null */ this._lightNodes = null; /** * A hash for identifying the current light nodes setup. * * @private * @type {?string} * @default null */ this._lightNodesHash = null; /** * `LightsNode` sets this property to `true` by default. * * @type {boolean} * @default true */ this.global = true; } /** * Overwrites the default {@link Node#customCacheKey} implementation by including * light data into the cache key. * * @return {number} The custom cache key. */ customCacheKey() { const hashData = []; const lights = this._lights; for ( let i = 0; i < lights.length; i ++ ) { const light = lights[ i ]; hashData.push( light.id ); if ( light.isSpotLight === true ) { const hashValue = ( light.map !== null ) ? light.map.id : - 1; hashData.push( hashValue ); } } return hashArray( hashData ); } /** * 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 ) { if ( this._lightNodesHash === null ) { if ( this._lightNodes === null ) this.setupLightsNode( builder ); const hash = []; for ( const lightNode of this._lightNodes ) { hash.push( lightNode.getSelf().getHash() ); } this._lightNodesHash = 'lights-' + hash.join( ',' ); } return this._lightNodesHash; } analyze( builder ) { const properties = builder.getDataFromNode( this ); for ( const node of properties.nodes ) { node.build( builder ); } } /** * 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. */ setupLightsNode( builder ) { const lightNodes = []; const previousLightNodes = this._lightNodes; const lights = sortLights( this._lights ); const nodeLibrary = builder.renderer.library; for ( const light of lights ) { if ( light.isNode ) { lightNodes.push( nodeObject( light ) ); } else { let lightNode = null; if ( previousLightNodes !== null ) { lightNode = getLightNodeById( light.id, previousLightNodes ); // reuse existing light node } if ( lightNode === null ) { // find the corresponding node type for a given light const lightNodeClass = nodeLibrary.getLightNodeClass( light.constructor ); if ( lightNodeClass === null ) { console.warn( `LightsNode.setupNodeLights: Light node not found for ${ light.constructor.name }` ); continue; } let lightNode = null; if ( ! _lightsNodeRef.has( light ) ) { lightNode = nodeObject( new lightNodeClass( light ) ); _lightsNodeRef.set( light, lightNode ); } else { lightNode = _lightsNodeRef.get( light ); } lightNodes.push( lightNode ); } } } this._lightNodes = 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 ); } 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 ); } } getLightNodes( builder ) { if ( this._lightNodes === null ) this.setupLightsNode( builder ); return this._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; const properties = builder.getDataFromNode( this ); if ( lightingModel ) { const { totalDiffuseNode, totalSpecularNode } = this; context.outgoingLight = outgoingLightNode; const stack = builder.addStack(); // properties.nodes = stack.nodes; // 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 ); } context.material.transparent = true; } totalDiffuseNode.assign( totalDiffuse ); totalSpecularNode.assign( directSpecular.add( indirectSpecular ) ); outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) ); // lightingModel.finish( builder ); // outgoingLightNode = outgoingLightNode.bypass( builder.removeStack() ); } else { properties.nodes = []; } // 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; this._lightNodes = null; this._lightNodesHash = null; return this; } /** * Returns an array of the scene's lights. * * @return {Array<Light>} The scene's lights. */ getLights() { return this._lights; } /** * Whether the scene has lights or not. * * @type {boolean} */ get hasLights() { return this._lights.length > 0; } } export default LightsNode; /** * 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. */ export const lights = ( lights = [] ) => nodeObject( new LightsNode() ).setLights( lights );