From 898911aab05f05880ac46b4df749b9cb77881cae Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Tue, 15 Apr 2025 16:52:41 +0200 Subject: [PATCH 1/3] TSL: Add switch/case. --- src/Three.TSL.js | 1 + src/nodes/core/StackNode.js | 71 ++++++++++++++++++++++++++++++++++++- src/nodes/tsl/TSLCore.js | 1 + 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/Three.TSL.js b/src/Three.TSL.js index cefec7c04b45c2..52433143c5745f 100644 --- a/src/Three.TSL.js +++ b/src/Three.TSL.js @@ -13,6 +13,7 @@ export const F_Schlick = TSL.F_Schlick; export const Fn = TSL.Fn; export const INFINITY = TSL.INFINITY; export const If = TSL.If; +export const Switch = TSL.Switch; export const Loop = TSL.Loop; export const NodeShaderStage = TSL.NodeShaderStage; export const NodeType = TSL.NodeType; diff --git a/src/nodes/core/StackNode.js b/src/nodes/core/StackNode.js index a51d82a41a7da2..5e9780bbfc85fd 100644 --- a/src/nodes/core/StackNode.js +++ b/src/nodes/core/StackNode.js @@ -1,6 +1,6 @@ import Node from './Node.js'; import { select } from '../math/ConditionalNode.js'; -import { ShaderNode, nodeProxy, getCurrentStack, setCurrentStack } from '../tsl/TSLBase.js'; +import { ShaderNode, nodeProxy, getCurrentStack, setCurrentStack, nodeObject } from '../tsl/TSLBase.js'; /** * Stack is a helper for Nodes that need to produce stack-based code instead of continuous flow. @@ -57,6 +57,16 @@ class StackNode extends Node { */ this._currentCond = null; + /** + * The current value node. Only + * relevant for Switch/Case. + * + * @private + * @type {Node} + * @default null + */ + this._valueNode = null; + /** * This flag can be used for type testing. * @@ -143,6 +153,65 @@ class StackNode extends Node { } + /** + * Represents a `switch` statement in TSL. + * + * @param {any} expression - Represents the expression. + * @param {Function} method - TSL code which is executed if the condition evaluates to `true`. + * @return {StackNode} A reference to this stack node. + */ + Switch( expression ) { + + this._valueNode = nodeObject( expression ); + + return this; + + } + + /** + * Represents a `case` statement in TSL. + * + * @param {any} value - The value to compare the expression with. + * @param {Function} method - TSL code which is executed if the case block is executed. + * @return {StackNode} A reference to this stack node. + */ + Case( value, method ) { + + const methodNode = new ShaderNode( method ); + + const caseNode = select( this._valueNode.equal( nodeObject( value ) ), methodNode ); + + if ( this._currentCond === null ) { + + this._currentCond = caseNode; + + return this.add( this._currentCond ); + + } else { + + this._currentCond.elseNode = caseNode; + this._currentCond = caseNode; + + return this; + + } + + } + + /** + * Represents the default code block of a Switch/Case statement. + * + * @param {Function} method - TSL code which is executed in the `else` case. + * @return {StackNode} A reference to this stack node. + */ + Default( method ) { + + this.Else( method ); + + return this; + + } + build( builder, ...params ) { const previousStack = getCurrentStack(); diff --git a/src/nodes/tsl/TSLCore.js b/src/nodes/tsl/TSLCore.js index 52ad48d9f1397d..07a600a7a1f1fc 100644 --- a/src/nodes/tsl/TSLCore.js +++ b/src/nodes/tsl/TSLCore.js @@ -705,6 +705,7 @@ export const setCurrentStack = ( stack ) => { export const getCurrentStack = () => currentStack; export const If = ( ...params ) => currentStack.If( ...params ); +export const Switch = ( ...params ) => currentStack.Switch( ...params ); export function append( node ) { From 6967c66382c31e1c40bec308b9245c024f8b0227 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Wed, 16 Apr 2025 10:26:31 +0200 Subject: [PATCH 2/3] StackNode: Support Case() sequence. --- src/nodes/core/StackNode.js | 63 ++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/src/nodes/core/StackNode.js b/src/nodes/core/StackNode.js index 5e9780bbfc85fd..d4a916c8a4869e 100644 --- a/src/nodes/core/StackNode.js +++ b/src/nodes/core/StackNode.js @@ -58,14 +58,23 @@ class StackNode extends Node { this._currentCond = null; /** - * The current value node. Only + * The expression node. Only * relevant for Switch/Case. * * @private * @type {Node} * @default null */ - this._valueNode = null; + this._expressionNode = null; + + /** + * An array representing a sequence of Case() calls. + * + * @private + * @type {Node} + * @default null + */ + this._cases = []; /** * This flag can be used for type testing. @@ -162,7 +171,7 @@ class StackNode extends Node { */ Switch( expression ) { - this._valueNode = nodeObject( expression ); + this._expressionNode = nodeObject( expression ); return this; @@ -172,27 +181,53 @@ class StackNode extends Node { * Represents a `case` statement in TSL. * * @param {any} value - The value to compare the expression with. - * @param {Function} method - TSL code which is executed if the case block is executed. + * @param {?Function} [method=null] - TSL code which is executed if the case block is executed. * @return {StackNode} A reference to this stack node. */ - Case( value, method ) { + Case( value, method = null ) { - const methodNode = new ShaderNode( method ); - - const caseNode = select( this._valueNode.equal( nodeObject( value ) ), methodNode ); + if ( method === null ) { - if ( this._currentCond === null ) { + // store the case to build a condition node at a later point - this._currentCond = caseNode; + this._cases.push( this._expressionNode.equal( nodeObject( value ) ) ); - return this.add( this._currentCond ); + return this; } else { - this._currentCond.elseNode = caseNode; - this._currentCond = caseNode; + const methodNode = new ShaderNode( method ); - return this; + let caseNode = this._expressionNode.equal( nodeObject( value ) ); + + // process all subsequent cases without a method (optional) + + for ( const c of this._cases ) { + + caseNode = caseNode.or( c ); + + } + + this._cases.length = 0; + + // build condition + + const condNode = select( caseNode, methodNode ); + + if ( this._currentCond === null ) { + + this._currentCond = condNode; + + return this.add( this._currentCond ); + + } else { + + this._currentCond.elseNode = condNode; + this._currentCond = condNode; + + return this; + + } } From dcccb3620c1e0906bae08f30e59314ae11e558bf Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Thu, 17 Apr 2025 11:23:14 +0200 Subject: [PATCH 3/3] StackNode: Refactor `Case()`. --- src/nodes/core/StackNode.js | 66 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/src/nodes/core/StackNode.js b/src/nodes/core/StackNode.js index d4a916c8a4869e..76b0fed0ef8123 100644 --- a/src/nodes/core/StackNode.js +++ b/src/nodes/core/StackNode.js @@ -67,15 +67,6 @@ class StackNode extends Node { */ this._expressionNode = null; - /** - * An array representing a sequence of Case() calls. - * - * @private - * @type {Node} - * @default null - */ - this._cases = []; - /** * This flag can be used for type testing. * @@ -178,56 +169,63 @@ class StackNode extends Node { } /** - * Represents a `case` statement in TSL. + * Represents a `case` statement in TSL. The TSL version accepts an arbitrary numbers of values. + * The last parameter must be the callback method that should be executed in the `true` case. * - * @param {any} value - The value to compare the expression with. - * @param {?Function} [method=null] - TSL code which is executed if the case block is executed. + * @param {...any} params - The values of the `Case()` statement as well as the callback method. * @return {StackNode} A reference to this stack node. */ - Case( value, method = null ) { + Case( ...params ) { - if ( method === null ) { + const caseNodes = []; - // store the case to build a condition node at a later point + // extract case nodes from the parameter list - this._cases.push( this._expressionNode.equal( nodeObject( value ) ) ); + if ( params.length >= 2 ) { - return this; + for ( let i = 0; i < params.length - 1; i ++ ) { + + caseNodes.push( this._expressionNode.equal( nodeObject( params[ i ] ) ) ); + + } } else { - const methodNode = new ShaderNode( method ); + throw new Error( 'TSL: Invalid parameter length. Case() requires at least two parameters.' ); - let caseNode = this._expressionNode.equal( nodeObject( value ) ); + } - // process all subsequent cases without a method (optional) + // extract method - for ( const c of this._cases ) { + const method = params[ params.length - 1 ]; + const methodNode = new ShaderNode( method ); - caseNode = caseNode.or( c ); + // chain multiple cases when using Case( 1, 2, 3, () => {} ) - } + let caseNode = caseNodes[ 0 ]; - this._cases.length = 0; + for ( let i = 1; i < caseNodes.length; i ++ ) { - // build condition + caseNode = caseNode.or( caseNodes[ i ] ); - const condNode = select( caseNode, methodNode ); + } - if ( this._currentCond === null ) { + // build condition - this._currentCond = condNode; + const condNode = select( caseNode, methodNode ); - return this.add( this._currentCond ); + if ( this._currentCond === null ) { - } else { + this._currentCond = condNode; - this._currentCond.elseNode = condNode; - this._currentCond = condNode; + return this.add( this._currentCond ); - return this; + } else { - } + this._currentCond.elseNode = condNode; + this._currentCond = condNode; + + return this; }