Thanks. Looks like the documentation is sort of in transition between old docs (written in HTML files), new docs (written in JSDoc comments) (EDIT: oh these are generated to HTML side by side with hand-written docs?), and the wiki.
I read the page you linked, but some things aren’t immediately obvious. For example, you mentioned
and the wiki page mentions
The main difference is that Fn() creates a controllable environment, allowing the use of stack where you can use assign and conditional , while the classic function only allows inline approaches.
but then it does not go and show examples comparing the mentioned differences. The wiki page proceeds to show an example doesn’t demonstrate assign or conditional stuff, but a simpler function that works either way with or without Fn:
// tsl function
const oscSine = Fn( ( [ t = time ] ) => {
return t.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 );
} );
// inline function (no Fn)
export const oscSine = ( t = time ) => t.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 );
Maybe this can be updated to precisely explain the differences.
As one example, this code does not throw or log any error, and does not work:
const result = someNode.mul(otherNode)
result.lessThanEqual(0.5).discard() // does not work
mat.opacityNode = result
codepen
This code also does not work, but it does throw an error:
const result = someNode.mul(otherNode)
If(result.lessThanEqual(0.5), () => Discard()) // does not work, throws "Cannot read properties of null (reading 'If')"
mat.opacityNode = result
codepen
But both of the following work (the 1337 conditional check successfully logs the WGSL code to console):
const result = someNode.mul(otherNode)
const opacityNode = Fn(() => {
result.lessThanEqual(0.5).discard()
return result
})()
mat.opacityNode = opacityNode
codepen
const result = someNode.mul(otherNode)
const opacityNode = Fn(() => {
If(result.lessThanEqual(0.5), () => Discard())
return result
})()
mat.opacityNode = opacityNode
codepen
It isn’t clear why this is required. Seems to me like both versions without Fn (chaining vs If), could (should?) just work, as neither of those graphs (both attached to opacityNode) rely on any arguments such as material being passed in from the renderer, so they seem fully standalone.
Another question may be, what is it about the “node graph” (that is not very exposed via TSL, and remains more of an internal detail), that would prevent those things from simply working?
Is it that branches that lead to nowhere (i.e. dangling paths not leading to the final output node) are not tracked? If so, could a simple no-op merge fix that to join dangling paths into the final result? For example:
const result = someNode.mul(otherNode)
const discardCheck = result.lessThanEqual(0.5).discard()
mat.opacityNode = result.merge(discardCheck) // either the first node is only considered for the output
// or
mat.opacityNode = discardCheck.merge(result) // or the last node is only considered for the output
and
const result = someNode.mul(otherNode)
const discardCheck = If(result.lessThanEqual(0.5), () => Discard())
mat.opacityNode = result.merge(discardCheck) // either the first node is only considered for the output
// or
mat.opacityNode = discardCheck.merge(result) // or the last node is only considered for the output
which implies that the arrow function for Discard() would not be needed, because Fn would no longer be needed, and Discard() would simply be a declarative node connected in the graph:
const discardCheck = If(result.lessThanEqual(0.5), Discard()) // merges two nodes into a boolean.
The idea is, if the graph of nodes are ultimately connected, then they should all just work. And if dangling paths are too complex or undesirable to handle (too much perf overhead to traverse and find them?) then the merge idea could solve that: there’d always be an easy-to-follow path from any top-level input node to the final output node, or vice versa.
Can those use cases be made to just work without Fn?