UNPKG

three

Version:

JavaScript 3D library

579 lines (311 loc) 9.17 kB
import { EventDispatcher } from 'three'; import { NodeUpdateType } from './constants.js'; import { getNodeChildren, getCacheKey } from './NodeUtils.js'; import { MathUtils } from 'three'; const NodeClasses = new Map(); let _nodeId = 0; class Node extends EventDispatcher { constructor( nodeType = null ) { super(); this.nodeType = nodeType; this.updateType = NodeUpdateType.NONE; this.updateBeforeType = NodeUpdateType.NONE; this.updateAfterType = NodeUpdateType.NONE; this.uuid = MathUtils.generateUUID(); this.version = 0; this._cacheKey = null; this._cacheKeyVersion = 0; this.global = false; this.isNode = true; Object.defineProperty( this, 'id', { value: _nodeId ++ } ); } set needsUpdate( value ) { if ( value === true ) { this.version ++; } } get type() { return this.constructor.type; } onUpdate( callback, updateType ) { this.updateType = updateType; this.update = callback.bind( this.getSelf() ); return this; } onFrameUpdate( callback ) { return this.onUpdate( callback, NodeUpdateType.FRAME ); } onRenderUpdate( callback ) { return this.onUpdate( callback, NodeUpdateType.RENDER ); } onObjectUpdate( callback ) { return this.onUpdate( callback, NodeUpdateType.OBJECT ); } onReference( callback ) { this.updateReference = callback.bind( this.getSelf() ); return this; } getSelf() { // Returns non-node object. return this.self || this; } updateReference( /*state*/ ) { return this; } isGlobal( /*builder*/ ) { return this.global; } * getChildren() { for ( const { childNode } of getNodeChildren( this ) ) { yield childNode; } } dispose() { this.dispatchEvent( { type: 'dispose' } ); } traverse( callback ) { callback( this ); for ( const childNode of this.getChildren() ) { childNode.traverse( callback ); } } getCacheKey( force = false ) { force = force || this.version !== this._cacheKeyVersion; if ( force === true || this._cacheKey === null ) { this._cacheKey = getCacheKey( this, force ); this._cacheKeyVersion = this.version; } return this._cacheKey; } getHash( /*builder*/ ) { return this.uuid; } getUpdateType() { return this.updateType; } getUpdateBeforeType() { return this.updateBeforeType; } getUpdateAfterType() { return this.updateAfterType; } getElementType( builder ) { const type = this.getNodeType( builder ); const elementType = builder.getElementType( type ); return elementType; } getNodeType( builder ) { const nodeProperties = builder.getNodeProperties( this ); if ( nodeProperties.outputNode ) { return nodeProperties.outputNode.getNodeType( builder ); } return this.nodeType; } getShared( builder ) { const hash = this.getHash( builder ); const nodeFromHash = builder.getNodeFromHash( hash ); return nodeFromHash || this; } setup( builder ) { const nodeProperties = builder.getNodeProperties( this ); let index = 0; for ( const childNode of this.getChildren() ) { nodeProperties[ 'node' + index ++ ] = childNode; } // return a outputNode if exists return null; } construct( builder ) { // @deprecated, r157 console.warn( 'THREE.Node: construct() is deprecated. Use setup() instead.' ); return this.setup( builder ); } increaseUsage( builder ) { const nodeData = builder.getDataFromNode( this ); nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; return nodeData.usageCount; } analyze( builder ) { const usageCount = this.increaseUsage( builder ); if ( usageCount === 1 ) { // node flow children const nodeProperties = builder.getNodeProperties( this ); for ( const childNode of Object.values( nodeProperties ) ) { if ( childNode && childNode.isNode === true ) { childNode.build( builder ); } } } } generate( builder, output ) { const { outputNode } = builder.getNodeProperties( this ); if ( outputNode && outputNode.isNode === true ) { return outputNode.build( builder, output ); } } updateBefore( /*frame*/ ) { console.warn( 'Abstract function.' ); } updateAfter( /*frame*/ ) { console.warn( 'Abstract function.' ); } update( /*frame*/ ) { console.warn( 'Abstract function.' ); } build( builder, output = null ) { const refNode = this.getShared( builder ); if ( this !== refNode ) { return refNode.build( builder, output ); } builder.addNode( this ); builder.addChain( this ); /* Build stages expected results: - "setup" -> Node - "analyze" -> null - "generate" -> String */ let result = null; const buildStage = builder.getBuildStage(); if ( buildStage === 'setup' ) { this.updateReference( builder ); const properties = builder.getNodeProperties( this ); if ( properties.initialized !== true ) { const stackNodesBeforeSetup = builder.stack.nodes.length; properties.initialized = true; properties.outputNode = this.setup( builder ); if ( properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup ) { properties.outputNode = builder.stack; } for ( const childNode of Object.values( properties ) ) { if ( childNode && childNode.isNode === true ) { childNode.build( builder ); } } } } else if ( buildStage === 'analyze' ) { this.analyze( builder ); } else if ( buildStage === 'generate' ) { const isGenerateOnce = this.generate.length === 1; if ( isGenerateOnce ) { const type = this.getNodeType( builder ); const nodeData = builder.getDataFromNode( this ); result = nodeData.snippet; if ( result === undefined ) { result = this.generate( builder ) || ''; nodeData.snippet = result; } result = builder.format( result, type, output ); } else { result = this.generate( builder, output ) || ''; } } builder.removeChain( this ); return result; } getSerializeChildren() { return getNodeChildren( this ); } serialize( json ) { const nodeChildren = this.getSerializeChildren(); const inputNodes = {}; for ( const { property, index, childNode } of nodeChildren ) { if ( index !== undefined ) { if ( inputNodes[ property ] === undefined ) { inputNodes[ property ] = Number.isInteger( index ) ? [] : {}; } inputNodes[ property ][ index ] = childNode.toJSON( json.meta ).uuid; } else { inputNodes[ property ] = childNode.toJSON( json.meta ).uuid; } } if ( Object.keys( inputNodes ).length > 0 ) { json.inputNodes = inputNodes; } } deserialize( json ) { if ( json.inputNodes !== undefined ) { const nodes = json.meta.nodes; for ( const property in json.inputNodes ) { if ( Array.isArray( json.inputNodes[ property ] ) ) { const inputArray = []; for ( const uuid of json.inputNodes[ property ] ) { inputArray.push( nodes[ uuid ] ); } this[ property ] = inputArray; } else if ( typeof json.inputNodes[ property ] === 'object' ) { const inputObject = {}; for ( const subProperty in json.inputNodes[ property ] ) { const uuid = json.inputNodes[ property ][ subProperty ]; inputObject[ subProperty ] = nodes[ uuid ]; } this[ property ] = inputObject; } else { const uuid = json.inputNodes[ property ]; this[ property ] = nodes[ uuid ]; } } } } toJSON( meta ) { const { uuid, type } = this; const isRoot = ( meta === undefined || typeof meta === 'string' ); if ( isRoot ) { meta = { textures: {}, images: {}, nodes: {} }; } // serialize let data = meta.nodes[ uuid ]; if ( data === undefined ) { data = { uuid, type, meta, metadata: { version: 4.6, type: 'Node', generator: 'Node.toJSON' } }; if ( isRoot !== true ) meta.nodes[ data.uuid ] = data; this.serialize( data ); delete data.meta; } // TODO: Copied from Object3D.toJSON function extractFromCache( cache ) { const values = []; for ( const key in cache ) { const data = cache[ key ]; delete data.metadata; values.push( data ); } return values; } if ( isRoot ) { const textures = extractFromCache( meta.textures ); const images = extractFromCache( meta.images ); const nodes = extractFromCache( meta.nodes ); if ( textures.length > 0 ) data.textures = textures; if ( images.length > 0 ) data.images = images; if ( nodes.length > 0 ) data.nodes = nodes; } return data; } } export default Node; export function addNodeClass( type, nodeClass ) { if ( typeof nodeClass !== 'function' || ! type ) throw new Error( `Node class ${ type } is not a class` ); if ( NodeClasses.has( type ) ) { console.warn( `Redefinition of node class ${ type }` ); return; } NodeClasses.set( type, nodeClass ); nodeClass.type = type; } export function createNodeFromType( type ) { const Class = NodeClasses.get( type ); if ( Class !== undefined ) { return new Class(); } }