Skip to content

Commit 7c01f6e

Browse files
authored
TSL: Introduce array() (mrdoob#30386)
* introduce array * updates * added `.toArray()`
1 parent b18d74c commit 7c01f6e

File tree

11 files changed

+271
-14
lines changed

11 files changed

+271
-14
lines changed

src/Three.TSL.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const anisotropyB = TSL.anisotropyB;
4343
export const anisotropyT = TSL.anisotropyT;
4444
export const any = TSL.any;
4545
export const append = TSL.append;
46+
export const array = TSL.array;
4647
export const arrayBuffer = TSL.arrayBuffer;
4748
export const asin = TSL.asin;
4849
export const assign = TSL.assign;

src/nodes/Nodes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
export * from './core/constants.js';
33

44
// core
5+
export { default as ArrayNode } from './core/ArrayNode.js';
56
export { default as AssignNode } from './core/AssignNode.js';
67
export { default as AttributeNode } from './core/AttributeNode.js';
78
export { default as BypassNode } from './core/BypassNode.js';

src/nodes/core/ArrayNode.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import TempNode from './TempNode.js';
2+
import { addMethodChaining, nodeObject } from '../tsl/TSLCore.js';
3+
4+
/** @module ArrayNode **/
5+
6+
/**
7+
* ArrayNode represents a collection of nodes, typically created using the {@link module:TSL~array} function.
8+
* ```js
9+
* const colors = array( [
10+
* vec3( 1, 0, 0 ),
11+
* vec3( 0, 1, 0 ),
12+
* vec3( 0, 0, 1 )
13+
* ] );
14+
*
15+
* const redColor = tintColors.element( 0 );
16+
*
17+
* @augments Node
18+
*/
19+
class ArrayNode extends TempNode {
20+
21+
static get type() {
22+
23+
return 'ArrayNode';
24+
25+
}
26+
27+
/**
28+
* Constructs a new array node.
29+
*
30+
* @param {String} [nodeType] - The data type of the elements.
31+
* @param {Number} [count] - Size of the array.
32+
* @param {Array<Node>?} [values=null] - Array default values.
33+
*/
34+
constructor( nodeType, count, values = null ) {
35+
36+
super( nodeType );
37+
38+
/**
39+
* Array size.
40+
*
41+
* @type {Array<Node>}
42+
*/
43+
this.count = count;
44+
45+
/**
46+
* Array default values.
47+
*
48+
* @type {Array<Node>}
49+
*/
50+
this.values = values;
51+
52+
/**
53+
* This flag can be used for type testing.
54+
*
55+
* @type {Boolean}
56+
* @readonly
57+
* @default true
58+
*/
59+
this.isArrayNode = true;
60+
61+
}
62+
63+
getNodeType( builder ) {
64+
65+
if ( this.nodeType === null ) {
66+
67+
this.nodeType = this.values[ 0 ].getNodeType( builder );
68+
69+
}
70+
71+
return this.nodeType;
72+
73+
}
74+
75+
getElementType( builder ) {
76+
77+
return this.getNodeType( builder );
78+
79+
}
80+
81+
generate( builder ) {
82+
83+
const type = this.getNodeType( builder );
84+
85+
return builder.generateArray( type, this.count, this.values );
86+
87+
}
88+
89+
}
90+
91+
export default ArrayNode;
92+
93+
/**
94+
* TSL function for creating an array node.
95+
*
96+
* @function
97+
* @param {String|Array<Node>} nodeTypeOrValues - A string representing the element type (e.g., 'vec3')
98+
* or an array containing the default values (e.g., [ vec3() ]).
99+
* @param {Number?} [count] - Size of the array.
100+
* @returns {ArrayNode}
101+
*/
102+
export const array = ( ...params ) => {
103+
104+
let node;
105+
106+
if ( params.length === 1 ) {
107+
108+
const values = params[ 0 ];
109+
110+
node = new ArrayNode( null, values.length, values );
111+
112+
} else {
113+
114+
const nodeType = params[ 0 ];
115+
const count = params[ 1 ];
116+
117+
node = new ArrayNode( nodeType, count );
118+
119+
}
120+
121+
return nodeObject( node );
122+
123+
};
124+
125+
addMethodChaining( 'toArray', ( node, count ) => array( Array( count ).fill( node ) ) );

src/nodes/core/NodeBuilder.js

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,55 @@ class NodeBuilder {
10531053

10541054
}
10551055

1056+
/**
1057+
* Generates the array declaration string.
1058+
*
1059+
* @param {String} type - The type.
1060+
* @param {Number?} [count] - The count.
1061+
* @return {String} The generated value as a shader string.
1062+
*/
1063+
generateArrayDeclaration( type, count ) {
1064+
1065+
return this.getType( type ) + '[ ' + count + ' ]';
1066+
1067+
}
1068+
1069+
/**
1070+
* Generates the array shader string for the given type and value.
1071+
*
1072+
* @param {String} type - The type.
1073+
* @param {Number?} [count] - The count.
1074+
* @param {Array<Node>?} [values=null] - The default values.
1075+
* @return {String} The generated value as a shader string.
1076+
*/
1077+
generateArray( type, count, values = null ) {
1078+
1079+
let snippet = this.generateArrayDeclaration( type, count ) + '( ';
1080+
1081+
for ( let i = 0; i < count; i ++ ) {
1082+
1083+
const value = values ? values[ i ] : null;
1084+
1085+
if ( value !== null ) {
1086+
1087+
snippet += value.build( this, type );
1088+
1089+
} else {
1090+
1091+
snippet += this.generateConst( type );
1092+
1093+
}
1094+
1095+
if ( i < count - 1 ) snippet += ', ';
1096+
1097+
}
1098+
1099+
snippet += ' )';
1100+
1101+
return snippet;
1102+
1103+
}
1104+
10561105
/**
10571106
* Generates the shader string for the given type and value.
10581107
*
@@ -1604,6 +1653,23 @@ class NodeBuilder {
16041653

16051654
}
16061655

1656+
/**
1657+
* Returns the array length.
1658+
*
1659+
* @param {Node} node - The node.
1660+
* @return {Number?} The array length.
1661+
*/
1662+
getArrayCount( node ) {
1663+
1664+
let count = null;
1665+
1666+
if ( node.isArrayNode ) count = node.count;
1667+
else if ( node.isVarNode && node.node.isArrayNode ) count = node.node.count;
1668+
1669+
return count;
1670+
1671+
}
1672+
16071673
/**
16081674
* Returns an instance of {@link NodeVar} for the given variable node.
16091675
*
@@ -1636,7 +1702,11 @@ class NodeBuilder {
16361702

16371703
}
16381704

1639-
nodeVar = new NodeVar( name, type, readOnly );
1705+
//
1706+
1707+
const count = this.getArrayCount( node );
1708+
1709+
nodeVar = new NodeVar( name, type, readOnly, count );
16401710

16411711
if ( ! readOnly ) {
16421712

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

1744+
} else if ( node.isArrayNode ) {
1745+
1746+
if ( node.values !== null ) {
1747+
1748+
for ( const n of node.values ) {
1749+
1750+
if ( ! this.isDeterministic( n ) ) {
1751+
1752+
return false;
1753+
1754+
}
1755+
1756+
}
1757+
1758+
}
1759+
1760+
return true;
1761+
16741762
} else if ( node.isConstNode ) {
16751763

16761764
return true;
@@ -2134,11 +2222,12 @@ class NodeBuilder {
21342222
*
21352223
* @param {String} type - The variable's type.
21362224
* @param {String} name - The variable's name.
2225+
* @param {Number?} [count=null] - The array length.
21372226
* @return {String} The shader string.
21382227
*/
2139-
getVar( type, name ) {
2228+
getVar( type, name, count = null ) {
21402229

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

21432232
}
21442233

src/nodes/core/NodeVar.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ class NodeVar {
1212
* @param {String} name - The name of the variable.
1313
* @param {String} type - The type of the variable.
1414
* @param {Boolean} [readOnly=false] - The read-only flag.
15+
* @param {Number?} [count=null] - The size.
1516
*/
16-
constructor( name, type, readOnly = false ) {
17+
constructor( name, type, readOnly = false, count = null ) {
1718

1819
/**
1920
* This flag can be used for type testing.
@@ -41,10 +42,17 @@ class NodeVar {
4142
/**
4243
* The read-only flag.
4344
*
44-
* @type {boolean}
45+
* @type {Boolean}
4546
*/
4647
this.readOnly = readOnly;
4748

49+
/**
50+
* The size.
51+
*
52+
* @type {Number?}
53+
*/
54+
this.count = count;
55+
4856
}
4957

5058
}

src/nodes/core/TempNode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class TempNode extends Node {
6868
const nodeVar = builder.getVarFromNode( this, null, type );
6969
const propertyName = builder.getPropertyName( nodeVar );
7070

71-
builder.addLineFlowCode( `${propertyName} = ${snippet}`, this );
71+
builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this );
7272

7373
nodeData.snippet = snippet;
7474
nodeData.propertyName = propertyName;

src/nodes/core/VarNode.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ class VarNode extends Node {
8282

8383
}
8484

85+
getElementType( builder ) {
86+
87+
return this.node.getElementType( builder );
88+
89+
}
90+
8591
getNodeType( builder ) {
8692

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

118124
if ( shouldTreatAsReadOnly ) {
119125

120-
const type = builder.getType( nodeVar.type );
121-
122126
if ( isWebGPUBackend ) {
123127

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

128132
} else {
129133

130-
declarationPrefix = `const ${ type } ${ propertyName }`;
134+
const count = builder.getArrayCount( node );
135+
136+
declarationPrefix = `const ${ builder.getVar( nodeVar.type, propertyName, count ) }`;
131137

132138
}
133139

src/nodes/tsl/TSLBase.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// TSL Base Syntax
33

44
export * from './TSLCore.js'; // float(), vec2(), vec3(), vec4(), mat3(), mat4(), Fn(), If(), element(), nodeObject(), nodeProxy(), ...
5+
export * from '../core/ArrayNode.js'; // array(), .toArray()
56
export * from '../core/UniformNode.js'; // uniform()
67
export * from '../core/PropertyNode.js'; // property() <-> TODO: Separate Material Properties in other file
78
export * from '../core/AssignNode.js'; // .assign()

src/nodes/utils/ArrayElementNode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class ArrayElementNode extends Node { // @TODO: If extending from TempNode it br
6666
const nodeSnippet = this.node.build( builder );
6767
const indexSnippet = this.indexNode.build( builder, 'uint' );
6868

69-
return `${nodeSnippet}[ ${indexSnippet} ]`;
69+
return `${ nodeSnippet }[ ${ indexSnippet } ]`;
7070

7171
}
7272

src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ ${ flowData.code }
496496

497497
for ( const variable of vars ) {
498498

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

501501
}
502502

0 commit comments

Comments
 (0)