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