Skip to content

Commit

Permalink
TSL: Introduce array() (#30386)
Browse files Browse the repository at this point in the history
* introduce array

* updates

* added `.toArray()`
  • Loading branch information
sunag authored Jan 23, 2025
1 parent b18d74c commit 7c01f6e
Show file tree
Hide file tree
Showing 11 changed files with 271 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/Three.TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const anisotropyB = TSL.anisotropyB;
export const anisotropyT = TSL.anisotropyT;
export const any = TSL.any;
export const append = TSL.append;
export const array = TSL.array;
export const arrayBuffer = TSL.arrayBuffer;
export const asin = TSL.asin;
export const assign = TSL.assign;
Expand Down
1 change: 1 addition & 0 deletions src/nodes/Nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
export * from './core/constants.js';

// core
export { default as ArrayNode } from './core/ArrayNode.js';
export { default as AssignNode } from './core/AssignNode.js';
export { default as AttributeNode } from './core/AttributeNode.js';
export { default as BypassNode } from './core/BypassNode.js';
Expand Down
125 changes: 125 additions & 0 deletions src/nodes/core/ArrayNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import TempNode from './TempNode.js';
import { addMethodChaining, nodeObject } from '../tsl/TSLCore.js';

/** @module ArrayNode **/

/**
* ArrayNode represents a collection of nodes, typically created using the {@link module:TSL~array} function.
* ```js
* const colors = array( [
* vec3( 1, 0, 0 ),
* vec3( 0, 1, 0 ),
* vec3( 0, 0, 1 )
* ] );
*
* const redColor = tintColors.element( 0 );
*
* @augments Node
*/
class ArrayNode extends TempNode {

static get type() {

return 'ArrayNode';

}

/**
* Constructs a new array node.
*
* @param {String} [nodeType] - The data type of the elements.
* @param {Number} [count] - Size of the array.
* @param {Array<Node>?} [values=null] - Array default values.
*/
constructor( nodeType, count, values = null ) {

super( nodeType );

/**
* Array size.
*
* @type {Array<Node>}
*/
this.count = count;

/**
* Array default values.
*
* @type {Array<Node>}
*/
this.values = values;

/**
* This flag can be used for type testing.
*
* @type {Boolean}
* @readonly
* @default true
*/
this.isArrayNode = true;

}

getNodeType( builder ) {

if ( this.nodeType === null ) {

this.nodeType = this.values[ 0 ].getNodeType( builder );

}

return this.nodeType;

}

getElementType( builder ) {

return this.getNodeType( builder );

}

generate( builder ) {

const type = this.getNodeType( builder );

return builder.generateArray( type, this.count, this.values );

}

}

export default ArrayNode;

/**
* TSL function for creating an array node.
*
* @function
* @param {String|Array<Node>} nodeTypeOrValues - A string representing the element type (e.g., 'vec3')
* or an array containing the default values (e.g., [ vec3() ]).
* @param {Number?} [count] - Size of the array.
* @returns {ArrayNode}
*/
export const array = ( ...params ) => {

let node;

if ( params.length === 1 ) {

const values = params[ 0 ];

node = new ArrayNode( null, values.length, values );

} else {

const nodeType = params[ 0 ];
const count = params[ 1 ];

node = new ArrayNode( nodeType, count );

}

return nodeObject( node );

};

addMethodChaining( 'toArray', ( node, count ) => array( Array( count ).fill( node ) ) );
95 changes: 92 additions & 3 deletions src/nodes/core/NodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,55 @@ class NodeBuilder {

}

/**
* Generates the array declaration string.
*
* @param {String} type - The type.
* @param {Number?} [count] - The count.
* @return {String} The generated value as a shader string.
*/
generateArrayDeclaration( type, count ) {

return this.getType( type ) + '[ ' + count + ' ]';

}

/**
* Generates the array shader string for the given type and value.
*
* @param {String} type - The type.
* @param {Number?} [count] - The count.
* @param {Array<Node>?} [values=null] - The default values.
* @return {String} The generated value as a shader string.
*/
generateArray( type, count, values = null ) {

let snippet = this.generateArrayDeclaration( type, count ) + '( ';

for ( let i = 0; i < count; i ++ ) {

const value = values ? values[ i ] : null;

if ( value !== null ) {

snippet += value.build( this, type );

} else {

snippet += this.generateConst( type );

}

if ( i < count - 1 ) snippet += ', ';

}

snippet += ' )';

return snippet;

}

/**
* Generates the shader string for the given type and value.
*
Expand Down Expand Up @@ -1604,6 +1653,23 @@ class NodeBuilder {

}

/**
* Returns the array length.
*
* @param {Node} node - The node.
* @return {Number?} The array length.
*/
getArrayCount( node ) {

let count = null;

if ( node.isArrayNode ) count = node.count;
else if ( node.isVarNode && node.node.isArrayNode ) count = node.node.count;

return count;

}

/**
* Returns an instance of {@link NodeVar} for the given variable node.
*
Expand Down Expand Up @@ -1636,7 +1702,11 @@ class NodeBuilder {

}

nodeVar = new NodeVar( name, type, readOnly );
//

const count = this.getArrayCount( node );

nodeVar = new NodeVar( name, type, readOnly, count );

if ( ! readOnly ) {

Expand Down Expand Up @@ -1671,6 +1741,24 @@ class NodeBuilder {
return this.isDeterministic( node.aNode ) &&
( node.bNode ? this.isDeterministic( node.bNode ) : true );

} else if ( node.isArrayNode ) {

if ( node.values !== null ) {

for ( const n of node.values ) {

if ( ! this.isDeterministic( n ) ) {

return false;

}

}

}

return true;

} else if ( node.isConstNode ) {

return true;
Expand Down Expand Up @@ -2134,11 +2222,12 @@ class NodeBuilder {
*
* @param {String} type - The variable's type.
* @param {String} name - The variable's name.
* @param {Number?} [count=null] - The array length.
* @return {String} The shader string.
*/
getVar( type, name ) {
getVar( type, name, count = null ) {

return `${ this.getType( type ) } ${ name }`;
return `${ count !== null ? this.generateArrayDeclaration( type, count ) : this.getType( type ) } ${ name }`;

}

Expand Down
12 changes: 10 additions & 2 deletions src/nodes/core/NodeVar.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ class NodeVar {
* @param {String} name - The name of the variable.
* @param {String} type - The type of the variable.
* @param {Boolean} [readOnly=false] - The read-only flag.
* @param {Number?} [count=null] - The size.
*/
constructor( name, type, readOnly = false ) {
constructor( name, type, readOnly = false, count = null ) {

/**
* This flag can be used for type testing.
Expand Down Expand Up @@ -41,10 +42,17 @@ class NodeVar {
/**
* The read-only flag.
*
* @type {boolean}
* @type {Boolean}
*/
this.readOnly = readOnly;

/**
* The size.
*
* @type {Number?}
*/
this.count = count;

}

}
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/core/TempNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class TempNode extends Node {
const nodeVar = builder.getVarFromNode( this, null, type );
const propertyName = builder.getPropertyName( nodeVar );

builder.addLineFlowCode( `${propertyName} = ${snippet}`, this );
builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this );

nodeData.snippet = snippet;
nodeData.propertyName = propertyName;
Expand Down
12 changes: 9 additions & 3 deletions src/nodes/core/VarNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ class VarNode extends Node {

}

getElementType( builder ) {

return this.node.getElementType( builder );

}

getNodeType( builder ) {

return this.node.getNodeType( builder );
Expand Down Expand Up @@ -117,8 +123,6 @@ class VarNode extends Node {

if ( shouldTreatAsReadOnly ) {

const type = builder.getType( nodeVar.type );

if ( isWebGPUBackend ) {

declarationPrefix = isDeterministic
Expand All @@ -127,7 +131,9 @@ class VarNode extends Node {

} else {

declarationPrefix = `const ${ type } ${ propertyName }`;
const count = builder.getArrayCount( node );

declarationPrefix = `const ${ builder.getVar( nodeVar.type, propertyName, count ) }`;

}

Expand Down
1 change: 1 addition & 0 deletions src/nodes/tsl/TSLBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// TSL Base Syntax

export * from './TSLCore.js'; // float(), vec2(), vec3(), vec4(), mat3(), mat4(), Fn(), If(), element(), nodeObject(), nodeProxy(), ...
export * from '../core/ArrayNode.js'; // array(), .toArray()
export * from '../core/UniformNode.js'; // uniform()
export * from '../core/PropertyNode.js'; // property() <-> TODO: Separate Material Properties in other file
export * from '../core/AssignNode.js'; // .assign()
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/utils/ArrayElementNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class ArrayElementNode extends Node { // @TODO: If extending from TempNode it br
const nodeSnippet = this.node.build( builder );
const indexSnippet = this.indexNode.build( builder, 'uint' );

return `${nodeSnippet}[ ${indexSnippet} ]`;
return `${ nodeSnippet }[ ${ indexSnippet } ]`;

}

Expand Down
2 changes: 1 addition & 1 deletion src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ ${ flowData.code }

for ( const variable of vars ) {

snippets.push( `${ this.getVar( variable.type, variable.name ) };` );
snippets.push( `${ this.getVar( variable.type, variable.name, variable.count ) };` );

}

Expand Down
Loading

0 comments on commit 7c01f6e

Please sign in to comment.