three
Version:
JavaScript 3D library
579 lines (311 loc) • 9.17 kB
JavaScript
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();
}
}