diff --git a/examples/webgpu_materials.html b/examples/webgpu_materials.html index 278117695ca5a1..b908c3e0e62a2d 100644 --- a/examples/webgpu_materials.html +++ b/examples/webgpu_materials.html @@ -241,7 +241,6 @@ // Scriptable - ScriptableNodeResources.set( 'THREE', THREE ); ScriptableNodeResources.set( 'TSL', TSL ); const asyncNode = scriptable( js( ` @@ -420,7 +419,7 @@ function testSerialization( mesh ) { const json = mesh.toJSON(); - const loader = new THREE.NodeObjectLoader().setNodes( moduleToLib( TSL ) ).setNodeMaterials( moduleToLib( THREE ) ); + const loader = new THREE.NodeObjectLoader().setNodes( moduleToLib( THREE ) ).setNodeMaterials( moduleToLib( THREE ) ); const serializedMesh = loader.parse( json ); serializedMesh.position.x = ( objects.length % 4 ) * 200 - 400; diff --git a/src/loaders/nodes/NodeLoader.js b/src/loaders/nodes/NodeLoader.js index 0ee39c773a39d6..051f3f86ae683c 100644 --- a/src/loaders/nodes/NodeLoader.js +++ b/src/loaders/nodes/NodeLoader.js @@ -3,17 +3,46 @@ import { nodeObject, float } from '../../nodes/tsl/TSLBase.js'; import { Loader } from '../Loader.js'; import { FileLoader } from '../../loaders/FileLoader.js'; +/** + * A loader for loading node objects in the three.js JSON Object/Scene format. + * + * @augments Loader + */ class NodeLoader extends Loader { + /** + * Constructs a new node loader. + * + * @param {LoadingManager?} manager - A reference to a loading manager. + */ constructor( manager ) { super( manager ); + /** + * Represents a dictionary of textures. + * + * @type {Object} + */ this.textures = {}; + + /** + * Represents a dictionary of node types. + * + * @type {Object} + */ this.nodes = {}; } + /** + * Loads the node definitions from the given URL. + * + * @param {String} url - The path/URL of the file to be loaded. + * @param {Function} onLoad - Will be called when load completes. + * @param {Function} onProgress - Will be called while load progresses. + * @param {Function} onError - Will be called when errors are thrown during the loading process. + */ load( url, onLoad, onProgress, onError ) { const loader = new FileLoader( this.manager ); @@ -46,6 +75,12 @@ class NodeLoader extends Loader { } + /** + * Parse the node dependencies for the loaded node. + * + * @param {Object} json - The JSON definition + * @return {Object} A dictionary with node dependencies. + */ parseNodes( json ) { const nodes = {}; @@ -80,6 +115,12 @@ class NodeLoader extends Loader { } + /** + * Parses the node from the given JSON. + * + * @param {Object} json - The JSON definition + * @return {Node} The parsed node. + */ parse( json ) { const node = this.createNodeFromType( json.type ); @@ -98,6 +139,12 @@ class NodeLoader extends Loader { } + /** + * Defines the dictionary of textures. + * + * @param {Object} value - The texture library defines as ``. + * @return {NodeLoader} A reference to this loader. + */ setTextures( value ) { this.textures = value; @@ -105,6 +152,12 @@ class NodeLoader extends Loader { } + /** + * Defines the dictionary of node types. + * + * @param {Object} value - The node library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ setNodes( value ) { this.nodes = value; @@ -112,6 +165,12 @@ class NodeLoader extends Loader { } + /** + * Creates a node object from the given type. + * + * @param {String} type - The node type. + * @return {Node} The created node instance. + */ createNodeFromType( type ) { if ( this.nodes[ type ] === undefined ) { diff --git a/src/loaders/nodes/NodeMaterialLoader.js b/src/loaders/nodes/NodeMaterialLoader.js index ec624f73022422..80d7a6c49dfd71 100644 --- a/src/loaders/nodes/NodeMaterialLoader.js +++ b/src/loaders/nodes/NodeMaterialLoader.js @@ -1,16 +1,43 @@ import { MaterialLoader } from '../../loaders/MaterialLoader.js'; +/** + * A special type of material loader for loading node materials. + * + * @augments MaterialLoader + */ class NodeMaterialLoader extends MaterialLoader { + /** + * Constructs a new node material loader. + * + * @param {LoadingManager?} manager - A reference to a loading manager. + */ constructor( manager ) { super( manager ); + /** + * Represents a dictionary of node types. + * + * @type {Object} + */ this.nodes = {}; + + /** + * Represents a dictionary of node material types. + * + * @type {Object} + */ this.nodeMaterials = {}; } + /** + * Parses the node material from the given JSON. + * + * @param {Object} json - The JSON definition + * @return {NodeMaterial}. The parsed material. + */ parse( json ) { const material = super.parse( json ); @@ -30,6 +57,12 @@ class NodeMaterialLoader extends MaterialLoader { } + /** + * Defines the dictionary of node types. + * + * @param {Object} value - The node library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ setNodes( value ) { this.nodes = value; @@ -37,6 +70,12 @@ class NodeMaterialLoader extends MaterialLoader { } + /** + * Defines the dictionary of node material types. + * + * @param {Object} value - The node material library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ setNodeMaterials( value ) { this.nodeMaterials = value; @@ -44,6 +83,12 @@ class NodeMaterialLoader extends MaterialLoader { } + /** + * Creates a node material from the given type. + * + * @param {String} type - The node material type. + * @return {Node} The created node material instance. + */ createMaterialFromType( type ) { const materialClass = this.nodeMaterials[ type ]; diff --git a/src/loaders/nodes/NodeObjectLoader.js b/src/loaders/nodes/NodeObjectLoader.js index 105abcf6c638a6..e42bfbaf28b444 100644 --- a/src/loaders/nodes/NodeObjectLoader.js +++ b/src/loaders/nodes/NodeObjectLoader.js @@ -3,19 +3,53 @@ import NodeMaterialLoader from './NodeMaterialLoader.js'; import { ObjectLoader } from '../../loaders/ObjectLoader.js'; +/** + * A special type of object loader for loading 3D objects using + * node materials. + * + * @augments ObjectLoader + */ class NodeObjectLoader extends ObjectLoader { + /** + * Constructs a new node object loader. + * + * @param {LoadingManager?} manager - A reference to a loading manager. + */ constructor( manager ) { super( manager ); + /** + * Represents a dictionary of node types. + * + * @type {Object} + */ this.nodes = {}; + + /** + * Represents a dictionary of node material types. + * + * @type {Object} + */ this.nodeMaterials = {}; + /** + * A reference for holdng the `nodes` JSON property. + * + * @private + * @type {Object?} + */ this._nodesJSON = null; } + /** + * Defines the dictionary of node types. + * + * @param {Object} value - The node library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ setNodes( value ) { this.nodes = value; @@ -23,6 +57,12 @@ class NodeObjectLoader extends ObjectLoader { } + /** + * Defines the dictionary of node material types. + * + * @param {Object} value - The node material library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ setNodeMaterials( value ) { this.nodeMaterials = value; @@ -30,6 +70,13 @@ class NodeObjectLoader extends ObjectLoader { } + /** + * Parses the node objects from the given JSON. + * + * @param {Object} json - The JSON definition + * @param {Function} onLoad - The onLoad callback function. + * @return {Object3D}. The parsed 3D object. + */ parse( json, onLoad ) { this._nodesJSON = json.nodes; @@ -42,6 +89,13 @@ class NodeObjectLoader extends ObjectLoader { } + /** + * Parses the node objects from the given JSON and textures. + * + * @param {Object} json - The JSON definition + * @param {Object} textures - The texture library. + * @return {Object}. The parsed nodes. + */ parseNodes( json, textures ) { if ( json !== undefined ) { @@ -58,6 +112,13 @@ class NodeObjectLoader extends ObjectLoader { } + /** + * Parses the node objects from the given JSON and textures. + * + * @param {Object} json - The JSON definition + * @param {Object} textures - The texture library. + * @return {Object}. The parsed materials. + */ parseMaterials( json, textures ) { const materials = {}; diff --git a/src/nodes/code/ScriptableNode.js b/src/nodes/code/ScriptableNode.js index 90137e322d9af4..19ed0143e67449 100644 --- a/src/nodes/code/ScriptableNode.js +++ b/src/nodes/code/ScriptableNode.js @@ -3,6 +3,11 @@ import { scriptableValue } from './ScriptableValueNode.js'; import { nodeProxy, float } from '../tsl/TSLBase.js'; import { hashArray, hashString } from '../core/NodeUtils.js'; +/** + * A Map-like data structure for managing resources of scriptable nodes. + * + * @augments Map + */ class Resources extends Map { get( key, callback = null, ...params ) { @@ -58,8 +63,49 @@ class Parameters { } +/** + * Defines the resouces (e.g. namespaces) of scriptable nodes. + * + * @type {Resources} + */ export const ScriptableNodeResources = new Resources(); +/** + * This type of node allows to implement nodes with custom scripts. The script + * section is represented as an instance of `CodeNode` written with JavaScript. + * The script itself must adhere to a specific structure. + * + * - main(): Executed once by default and every time `node.needsUpdate` is set. + * - layout: The layout object defines the script's interface (inputs and outputs). + * + * ```js + * ScriptableNodeResources.set( 'TSL', TSL ); + * + * const scriptableNode = scriptable( js( ` + * layout = { + * outputType: 'node', + * elements: [ + * { name: 'source', inputType: 'node' }, + * ] + * }; + * + * const { mul, oscSine } = TSL; + * + * function main() { + * const source = parameters.get( 'source' ) || float(); + * return mul( source, oscSine() ) ); + * } + * + * ` ) ); + * + * scriptableNode.setParameter( 'source', color( 1, 0, 0 ) ); + * + * const material = new THREE.MeshBasicNodeMaterial(); + * material.colorNode = scriptableNode; + * ``` + * + * @augments Node + */ class ScriptableNode extends Node { static get type() { @@ -68,11 +114,30 @@ class ScriptableNode extends Node { } + /** + * Constructs a new scriptable node. + * + * @param {CodeNode?} [codeNode=null] - The code node. + * @param {Object} [parameters={}] - The parameters definition. + */ constructor( codeNode = null, parameters = {} ) { super(); + /** + * The code node. + * + * @type {CodeNode?} + * @default null + */ this.codeNode = codeNode; + + /** + * The parameters definition. + * + * @type {Object} + * @default {} + */ this.parameters = parameters; this._local = new Resources(); @@ -86,34 +151,68 @@ class ScriptableNode extends Node { this.onRefresh = this.onRefresh.bind( this ); + /** + * This flag can be used for type testing. + * + * @type {Boolean} + * @readonly + * @default true + */ this.isScriptableNode = true; } + /** + * The source code of the scriptable node. + * + * @type {String} + */ get source() { return this.codeNode ? this.codeNode.code : ''; } + /** + * Sets the reference of a local script variable. + * + * @param {String} name - The variable name. + * @param {Object} value - The reference to set. + * @return {Resources} The resource map + */ setLocal( name, value ) { return this._local.set( name, value ); } + /** + * Gets the value of a local script variable. + * + * @param {String} name - The variable name. + * @return {Object} The value. + */ getLocal( name ) { return this._local.get( name ); } + /** + * Event listener for the `refresh` event. + */ onRefresh() { this._refresh(); } + /** + * Returns an input from the layout with the given id/name. + * + * @param {String} id - The id/name of the input. + * @return {Object} The element entry. + */ getInputLayout( id ) { for ( const element of this.getLayout() ) { @@ -128,6 +227,12 @@ class ScriptableNode extends Node { } + /** + * Returns an output from the layout with the given id/name. + * + * @param {String} id - The id/name of the output. + * @return {Object} The element entry. + */ getOutputLayout( id ) { for ( const element of this.getLayout() ) { @@ -142,6 +247,13 @@ class ScriptableNode extends Node { } + /** + * Defines a script output for the given name and value. + * + * @param {String} name - The name of the output. + * @param {Node} value - The node value. + * @return {ScriptableNode} A reference to this node. + */ setOutput( name, value ) { const outputs = this._outputs; @@ -160,18 +272,37 @@ class ScriptableNode extends Node { } + /** + * Returns a script output for the given name. + * + * @param {String} name - The name of the output. + * @return {Node} The node value. + */ getOutput( name ) { return this._outputs[ name ]; } + /** + * Returns a paramater for the given name + * + * @param {String} name - The name of the parameter. + * @return {ScriptableValueNode} The node value. + */ getParameter( name ) { return this.parameters[ name ]; } + /** + * Sets a value for the given parameter name. + * + * @param {String} name - The parameter name. + * @param {Any} value - The parameter value. + * @return {ScriptableNode} A reference to this node. + */ setParameter( name, value ) { const parameters = this.parameters; @@ -205,12 +336,24 @@ class ScriptableNode extends Node { } + /** + * Returns the value of this node which is the value of + * the default output. + * + * @return {Node} The value. + */ getValue() { return this.getDefaultOutput().getValue(); } + /** + * Deletes a parameter from the script. + * + * @param {String} name - The parameter to remove. + * @return {ScriptableNode} A reference to this node. + */ deleteParameter( name ) { let valueNode = this.parameters[ name ]; @@ -227,6 +370,11 @@ class ScriptableNode extends Node { } + /** + * Deletes all parameters from the script. + * + * @return {ScriptableNode} A reference to this node. + */ clearParameters() { for ( const name of Object.keys( this.parameters ) ) { @@ -241,6 +389,13 @@ class ScriptableNode extends Node { } + /** + * Calls a function from the script. + * + * @param {String} name - The function name. + * @param {...Any} params - A list of parameters. + * @return {Any} The result of the function call. + */ call( name, ...params ) { const object = this.getObject(); @@ -254,6 +409,13 @@ class ScriptableNode extends Node { } + /** + * Asynchronously calls a function from the script. + * + * @param {String} name - The function name. + * @param {...Any} params - A list of parameters. + * @return {Any} The result of the function call. + */ async callAsync( name, ...params ) { const object = this.getObject(); @@ -267,12 +429,23 @@ class ScriptableNode extends Node { } + /** + * Overwritten since the node types is inferred from the script's output. + * + * @param {NodeBuilder} builder - The current node builder + * @return {String} The node type. + */ getNodeType( builder ) { return this.getDefaultOutputNode().getNodeType( builder ); } + /** + * Refreshes the script node. + * + * @param {String?} [output=null] - An optinal output. + */ refresh( output = null ) { if ( output !== null ) { @@ -287,6 +460,11 @@ class ScriptableNode extends Node { } + /** + * Returns an object representation of the script. + * + * @return {Object} The result object. + */ getObject() { if ( this.needsUpdate ) this.dispose(); @@ -368,12 +546,22 @@ class ScriptableNode extends Node { } + /** + * Returns the layout of the script. + * + * @return {Object} The script's layout. + */ getLayout() { return this.getObject().layout; } + /** + * Returns default node output of the script. + * + * @return {Node} The default node output. + */ getDefaultOutputNode() { const output = this.getDefaultOutput().value; @@ -388,12 +576,22 @@ class ScriptableNode extends Node { } + /** + * Returns default output of the script. + * + * @return {ScriptableValueNode} The default output. + */ getDefaultOutput() { return this._exec()._output; } + /** + * Returns a function created from the node's script. + * + * @return {Function} The function representing the node's code. + */ getMethod() { if ( this.needsUpdate ) this.dispose(); @@ -418,6 +616,9 @@ class ScriptableNode extends Node { } + /** + * Frees all internal resources. + */ dispose() { if ( this._method === null ) return; @@ -470,6 +671,12 @@ class ScriptableNode extends Node { } + /** + * Executes the `main` function of the script. + * + * @private + * @return {ScriptableNode} A reference to this node. + */ _exec() { if ( this.codeNode === null ) return this; @@ -488,6 +695,11 @@ class ScriptableNode extends Node { } + /** + * Executes the refresh. + * + * @private + */ _refresh() { this.needsUpdate = true; @@ -502,4 +714,12 @@ class ScriptableNode extends Node { export default ScriptableNode; +/** + * TSL function for creating a scriptable node. + * + * @function + * @param {CodeNode?} [codeNode=null] - The code node. + * @param {Object} [parameters={}] - The parameters definition. + * @returns {ScriptableNode} + */ export const scriptable = /*@__PURE__*/ nodeProxy( ScriptableNode ); diff --git a/src/nodes/code/ScriptableValueNode.js b/src/nodes/code/ScriptableValueNode.js index 29c8d86af37b74..c8d53674db11bc 100644 --- a/src/nodes/code/ScriptableValueNode.js +++ b/src/nodes/code/ScriptableValueNode.js @@ -4,6 +4,11 @@ import { nodeProxy, float } from '../tsl/TSLBase.js'; import { EventDispatcher } from '../../core/EventDispatcher.js'; +/** + * `ScriptableNode` uses this class to manage script inputs and outputs. + * + * @augments Node + */ class ScriptableValueNode extends Node { static get type() { @@ -12,22 +17,72 @@ class ScriptableValueNode extends Node { } + /** + * Constructs a new scriptable node. + * + * @param {Any} [value=null] - The value. + */ constructor( value = null ) { super(); + /** + * A reference to the value. + * + * @private + * @default null + */ this._value = value; + + /** + * Depending on the type of `_value`, this property might cache parsed data. + * + * @private + * @default null + */ this._cache = null; + /** + * If this node represents an input, this property represents the input type. + * + * @type {String?} + * @default null + */ this.inputType = null; + + /** + * If this node represents an output, this property represents the output type. + * + * @type {String?} + * @default null + */ this.outputType = null; + /** + * An event dispatcher for managing events. + * + * @type {EventDispatcher} + */ this.events = new EventDispatcher(); + /** + * This flag can be used for type testing. + * + * @type {Boolean} + * @readonly + * @default true + */ this.isScriptableValueNode = true; } + /** + * Whether this node represents an output or not. + * + * @type {Boolean} + * @readonly + * @default true + */ get isScriptableOutputNode() { return this.outputType !== null; @@ -54,18 +109,32 @@ class ScriptableValueNode extends Node { } + /** + * The node's value. + * + * @type {Any} + */ get value() { return this._value; } + /** + * Dispatches the `refresh` event. + */ refresh() { this.events.dispatchEvent( { type: 'refresh' } ); } + /** + * The `value` property usually represents a node or even binary data in form of array buffers. + * In this case, this method tries to return the actual value behind the complex type. + * + * @return {Any} The value. + */ getValue() { const value = this.value; @@ -93,6 +162,12 @@ class ScriptableValueNode extends Node { } + /** + * Overwritten since the node type is inferred from the value. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {String} The node type. + */ getNodeType( builder ) { return this.value && this.value.isNode ? this.value.getNodeType( builder ) : 'float'; @@ -167,4 +242,11 @@ class ScriptableValueNode extends Node { export default ScriptableValueNode; +/** + * TSL function for creating a scriptable value node. + * + * @function + * @param {Any} [value=null] - The value. + * @returns {ScriptableValueNode} + */ export const scriptableValue = /*@__PURE__*/ nodeProxy( ScriptableValueNode );