-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Branching and looping for p5.strands #8120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev-2.0
Are you sure you want to change the base?
Conversation
Co-authored-by: Perminder Singh <[email protected]>
Co-authored-by: Perminder Singh <[email protected]>
Co-authored-by: Perminder Singh <[email protected]>
case '>': return 'greaterThan'; | ||
case '>=': return 'greaterThanEqualTo'; | ||
case '>=': return 'greaterEqual'; | ||
case '<': return 'lessThan'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wondering that we don't need to have !=
, !==
cases right? Like if user uses any of the other operations maybe (**
) which are not in the case, can we throw an error message saying Unsupported operator
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, that's probably what will happen!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
**
might have to be handled differently, because unlike the rest, which get turned into a method in js but then back into an operator in GLSL, **
becomes pow
and needs to stay pow
in GLSL, which will need some special casing in the code. I'll just leave a TODO for that one for the future.
baseMaterialShader().modify(() => { | ||
const t = uniformFloat('t', () => millis()) | ||
getWorldInputs((inputs) => { | ||
inputs.position = inputs.position.add(dynamicNode([20, 25, 20]).mult(sin(inputs.position.y.mult(0.05).add(dynamicNode(t).mult(0.004))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dynamicNode
has been changed to strandsNode
since the refactor
const rightPixel = myp5.get(75, 25); | ||
assert.approximately(rightPixel[0], 127, 5); // 0.5 * 255 ≈ 127 | ||
}); | ||
test('handle if-else-if chains', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't get the black to show. Maybe it has something to do with the codegen, rather than the graph though? Take a look at the outputted code. I changed the colours from this test example to make it clearer for me. I guesss that the nested if statement doesn't know that it should be updating T0
and creates its own output variable instead?
let testShader;
async function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
testShader = baseMaterialShader().modify(() => {
const value = uniformFloat(() => 0.5); // middle value
getPixelInputs(inputs => {
let color = vec3(0.0);
if (value > 0.8) {
color = vec3(1.0, 0, 0); // white for high values
} else if (value > 0.3) {
color = vec3(0, 1, 0); // gray for medium values
} else {
color = vec3(0, 0, 1); // black for low values
}
inputs.color = [color, 1.0];
return inputs;
});
});
}
function draw() {
background(0);
shader(testShader);
testShader.setUniform('value', mouseX/width)
sphere(100)
}
(Inputs inputs) {
vec3 T0;
if (value > float(0.8000))
{
vec3 T1 = vec3(1.0000, 0.0000, 0.0000);
T0 = T1;
}
else
{
T0 = vec3(0.0000, 1.0000, 0.0000);
vec3 T2;
if (value > float(0.3000))
{
vec3 T3 = vec3(0.0000, 1.0000, 0.0000);
T2 = T3;
}
else
{
vec3 T4 = vec3(0.0000, 0.0000, 1.0000);
T2 = T4;
}
}
inputs.normal = inputs.normal;
inputs.texCoord = inputs.texCoord;
inputs.ambientLight = inputs.ambientLight;
inputs.ambientMaterial = inputs.ambientMaterial;
inputs.specularMaterial = inputs.specularMaterial;
inputs.emissiveMaterial = inputs.emissiveMaterial;
inputs.color = vec4(T0, 1.0000);
inputs.shininess = inputs.shininess;
inputs.metalness = inputs.metalness;
return inputs;
}
} | ||
} | ||
} | ||
// Second pass: find assignments to non-local variables |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible that the assignments arent being wrapped in strandsNode
calls because of the pass order? It's causing an error if you do
if (condition > 0.5) {
col = 1.0;
}
Instead of col = float(1)
on the second line above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch! just added a test for this and it failed, so I pushed an update to wrap all assignments in branches in strandsNode. then it works!
Resolves #7868
Changes
i++
again>=
operator properlyI refactored a bit in order to handle nested blocks more easily: there's now a
SCOPE_START
andSCOPE_END
block for{
and}
respectively, and I added anASSIGNMENT
statement that is currently only used to assign to phi variables in each branch.Details
The main challenge here is that we have to fully replace control flow (
if
/for
) in users' code into function calls so that these structures can generate nodes in our program graph. If we don't, they run in javascript, and are invisible to GLSL generation. For example, if you had a loop that runs 10 times that adds 1 each time, it would output the add 1 line 10 times rather than outputting a for loop.However, once we have a function call instead of real control flow, we also need a way to make sure that when the users' javascript subsequently references nodes that were updated in the control flow, they properly reference the modified value after the
if
orfor
and not the original value. For that, we make the function calls return updated values, and we generate JS code that assigns these updated values back to the original JS variables.If statements
You write an if statement like this:
It gets transpiled into this function call. Each branch callback gets a copy of the node so that it can modify it without affecting the original, and each branch callback then returns an object with modified versions of those variables. The whole if/else structure returns an object with nodes representing the output of the branch, and we then assign back values from that to the original variables.
This then gets compiled to the following GLSL:
For loops
You write a loop like this:
This gets transpiled into this format, where we use a strands function call, and have a callback function for each part of the for loop. The loop body is structured more like a
reduce
, taking in current state + loop iteration and returning next state. At the end of the for loop, the properties of the final state are assigned back to their original variables.This then gets turned into the following GLSL:
PR Checklist
npm run lint
passes