From d1521ac4a6f9f5899e7f1c536d062dc953bb90bd Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 10 Aug 2026 14:46:38 -0400 Subject: [PATCH 1/2] Examples: Make example links clickable in title & info. (#34207) Co-authored-by: Michael Herzog --- examples/example.css | 2 +- examples/webgpu_gaussian_splatting.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example.css b/examples/example.css index e7e2093563cb9e..8640f424d00d5a 100644 --- a/examples/example.css +++ b/examples/example.css @@ -28,7 +28,7 @@ a { pointer-events: none; } -#info > a { +#info a { pointer-events: auto; } diff --git a/examples/webgpu_gaussian_splatting.html b/examples/webgpu_gaussian_splatting.html index da5d78ee29713f..785fae52dc4208 100644 --- a/examples/webgpu_gaussian_splatting.html +++ b/examples/webgpu_gaussian_splatting.html @@ -24,7 +24,7 @@ three.jsGaussian Splatting - WebGPU TSL Gaussian splat renderer by Ben Houston. + WebGPU TSL Gaussian splat renderer by Ben Houston. From 87dc9e6f88a69c630e30c5e24576db1dabd495d6 Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 10 Aug 2026 18:04:29 -0300 Subject: [PATCH 2/2] TSL & Transpiler: Support parameterless loops (do ... while) (#34209) --- examples/jsm/transpiler/AST.js | 3 +- examples/jsm/transpiler/GLSLDecoder.js | 43 +++++++++++++++ examples/jsm/transpiler/TSLEncoder.js | 72 +++++++++++++++++++++++++- examples/jsm/transpiler/WGSLEncoder.js | 6 +++ src/nodes/utils/LoopNode.js | 31 ++++++++--- 5 files changed, 145 insertions(+), 10 deletions(-) diff --git a/examples/jsm/transpiler/AST.js b/examples/jsm/transpiler/AST.js index 549c116c0b74b0..c7d52f92374c4c 100644 --- a/examples/jsm/transpiler/AST.js +++ b/examples/jsm/transpiler/AST.js @@ -612,12 +612,13 @@ export class For extends ASTNode { export class While extends ASTNode { - constructor( condition, body = [] ) { + constructor( condition, body = [], doWhile = false ) { super(); this.condition = condition; this.body = body; + this.doWhile = doWhile; this.isWhile = true; diff --git a/examples/jsm/transpiler/GLSLDecoder.js b/examples/jsm/transpiler/GLSLDecoder.js index 3810eb90a4b718..0df82098c4c832 100644 --- a/examples/jsm/transpiler/GLSLDecoder.js +++ b/examples/jsm/transpiler/GLSLDecoder.js @@ -921,6 +921,45 @@ class GLSLDecoder { } + parseDoWhile() { + + this.readToken(); // skip 'do' + + let body; + + if ( this.getToken().str === '{' ) { + + body = this.parseBlock(); + + } else { + + body = [ this.parseExpression() ]; + + } + + if ( this.getToken() && this.getToken().str === 'while' ) { + + this.readToken(); // skip 'while' + + } else { + + throw new Error( 'THREE.GLSLDecoder: Expected \'while\' after \'do\' block' + this.getTokenPosition( this.getToken() ) ); + + } + + const condTokens = this.readTokensUntil( ')' ); + const condition = this.parseExpressionFromTokens( condTokens.slice( 1, - 1 ) ); + + if ( this.getToken() && this.getToken().str === ';' ) { + + this.readToken(); // skip trailing ';' + + } + + return new While( condition, body, true ); + + } + parseFor() { this.readToken(); // skip 'for' @@ -1219,6 +1258,10 @@ class GLSLDecoder { statement = this.parseWhile(); + } else if ( token.str === 'do' ) { + + statement = this.parseDoWhile(); + } else if ( token.str === 'switch' ) { statement = this.parseSwitch(); diff --git a/examples/jsm/transpiler/TSLEncoder.js b/examples/jsm/transpiler/TSLEncoder.js index 1f977828df8346..44233bfdffb0f0 100644 --- a/examples/jsm/transpiler/TSLEncoder.js +++ b/examples/jsm/transpiler/TSLEncoder.js @@ -481,6 +481,38 @@ ${ this.tab }} )`; } + isVariableUsed( node, varName ) { + + if ( ! node ) return false; + + if ( node.isAccessor && node.property === varName ) return true; + + for ( const key in node ) { + + if ( key === 'parent' ) continue; + + const child = node[ key ]; + + if ( Array.isArray( child ) ) { + + for ( const item of child ) { + + if ( this.isVariableUsed( item, varName ) ) return true; + + } + + } else if ( child && child.isASTNode ) { + + if ( this.isVariableUsed( child, varName ) ) return true; + + } + + } + + return false; + + } + emitLoop( node ) { const start = this.emitExpression( node.initialization.value ); @@ -518,7 +550,22 @@ ${ this.tab }} )`; } - let loopStr = `Loop( { start: ${ start }, end: ${ end + nameParam + typeParam + conditionParam + updateParam } }, ( { ${ name } } ) => {\n\n`; + let loopParams; + + if ( start === '0' && nameParam === '' && typeParam === '' && conditionParam === '' && updateParam === '' ) { + + loopParams = end; + + } else { + + loopParams = `{ start: ${ start }, end: ${ end + nameParam + typeParam + conditionParam + updateParam } }`; + + } + + const isUsed = this.isVariableUsed( node.body, name ); + const callbackParams = isUsed ? `( { ${ name } } ) =>` : '() =>'; + + let loopStr = `Loop( ${ loopParams }, ${ callbackParams } {\n\n`; loopStr += this.emitBody( node.body ) + '\n\n'; @@ -634,8 +681,31 @@ ${ this.tab }} )`; } + emitDoWhile( node ) { + + const condition = this.emitExpression( node.condition ); + const body = this.emitBody( node.body ); + + let loopStr = `Loop( () => {\n\n${ body }\n\n${ this.tab }\tIf( ${ condition }.not(), () => {\n\n${ this.tab }\t\tBreak();\n\n${ this.tab }\t} );\n\n`; + + loopStr += this.tab + '} )'; + + this.imports.add( 'Loop' ); + this.imports.add( 'If' ); + this.imports.add( 'Break' ); + + return loopStr; + + } + emitWhile( node ) { + if ( node.doWhile ) { + + return this.emitDoWhile( node ); + + } + const condition = this.emitExpression( node.condition ); let whileStr = `Loop( ${ condition }, () => {\n\n`; diff --git a/examples/jsm/transpiler/WGSLEncoder.js b/examples/jsm/transpiler/WGSLEncoder.js index 7995860711eca7..5738f1e580c0e6 100644 --- a/examples/jsm/transpiler/WGSLEncoder.js +++ b/examples/jsm/transpiler/WGSLEncoder.js @@ -509,6 +509,12 @@ class WGSLEncoder { const cond = this.emitExpression( node.condition ); const body = this.emitBody( node.body ); + if ( node.doWhile ) { + + return `loop {\n\n${ body }\n\n${ this.tab }\tcontinuing {\n${ this.tab }\t\tbreak if ( ! ( ${ cond } ) );\n${ this.tab }\t}\n${ this.tab }}`; + + } + return `while ( ${ cond } ) {\n\n${ body }\n\n${ this.tab }}`; } diff --git a/src/nodes/utils/LoopNode.js b/src/nodes/utils/LoopNode.js index 5f3a5906c50eda..451345b6db00ef 100644 --- a/src/nodes/utils/LoopNode.js +++ b/src/nodes/utils/LoopNode.js @@ -1,6 +1,6 @@ import Node from '../core/Node.js'; import { expression } from '../code/ExpressionNode.js'; -import { nodeArray, Fn } from '../tsl/TSLBase.js'; +import { nodeArray, Fn, bool } from '../tsl/TSLBase.js'; import { error } from '../../utils.js'; /** @@ -88,10 +88,11 @@ class LoopNode extends Node { // const inputs = {}; + const params = this._getInternalParams(); - for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) { + for ( let i = 0, l = params.length - 1; i < l; i ++ ) { - const param = this.params[ i ]; + const param = params[ i ]; const name = ( param.isNode !== true && param.name ) || this.getVarName( i ); const type = ( param.isNode !== true && param.type ) || 'int'; @@ -102,16 +103,16 @@ class LoopNode extends Node { const stack = builder.addStack(); - const fnCall = this.params[ this.params.length - 1 ]( inputs ); + const fnCall = params[ params.length - 1 ]( inputs ); properties.returnsNode = fnCall.context( { nodeLoop: fnCall } ); properties.stackNode = stack; - const baseParam = this.params[ 0 ]; + const baseParam = params[ 0 ]; if ( baseParam.isNode !== true && typeof baseParam.update === 'function' ) { - const fnUpdateCall = Fn( this.params[ 0 ].update )( inputs ); + const fnUpdateCall = Fn( baseParam.update )( inputs ); properties.updateNode = fnUpdateCall.context( { nodeLoop: fnUpdateCall } ); @@ -123,6 +124,20 @@ class LoopNode extends Node { } + _getInternalParams() { + + const params = this.params; + + if ( typeof params[ 0 ] === 'function' ) { + + return [ bool( true ), params[ 0 ] ]; + + } + + return params; + + } + setup( builder ) { // setup properties @@ -142,7 +157,7 @@ class LoopNode extends Node { const properties = this.getProperties( builder ); - const params = this.params; + const params = this._getInternalParams(); const stackNode = properties.stackNode; for ( let i = 0, l = params.length - 1; i < l; i ++ ) { @@ -306,7 +321,7 @@ class LoopNode extends Node { builder.removeFlowTab().addFlowCode( '\n' + builder.tab + stackSnippet ); - for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) { + for ( let i = 0, l = params.length - 1; i < l; i ++ ) { builder.addFlowCode( ( i === 0 ? '' : builder.tab ) + '}\n\n' ).removeFlowTab();