[p5.strands]: order of modifications changes between strands and outputted GLSL
Most appropriate sub-area of p5.js?
- [ ] Accessibility
- [ ] Color
- [ ] Core/Environment/Rendering
- [ ] Data
- [ ] DOM
- [ ] Events
- [ ] Image
- [ ] IO
- [ ] Math
- [ ] Typography
- [ ] Utilities
- [x] WebGL
- [ ] Build process
- [ ] Unit testing
- [ ] Internationalization
- [ ] Friendly errors
- [ ] Other (specify if possible)
p5.js version
2.0.3
Web browser and version
Firefox
Operating system
MacOS
Steps to reproduce this
I was writing a strands shader that modifies position and also texture coordinates, and uses the position to modify the texture coordinate:
textureMaterial = baseMaterialShader().modify(() => {
const t = uniformFloat(() => millis());
getWorldInputs((inputs) => {
let size = [600, 400];
inputs.texCoord = inputs.position.xy / size + 0.5;
inputs.position.y += 50 * sin(t * 0.001 + inputs.position.x * 0.01);
return inputs;
});
});
Importantly, I modify the position after using it to modify the texture coordinate. I expect the texture to stretch a lot as because the texture coordinates stay the same while the positions they're attached to move. However, the texture remains largely stable in the end result: https://editor.p5js.org/davepagurek/sketches/jCo1EFc07
When I call .inspectHooks(), I can see that the GLSL looks like this:
Vertex getWorldInputs(Vertex inputs) {
vec3 temp_0 = inputs.position;
temp_0 = vec3(temp_0.x, temp_0.y + (50.0000 * sin((t * 0.0010) + (temp_0.x * 0.0100))), temp_0.z);
vec2 temp_3 = vec2(temp_0.x, temp_0.y);
vec2 temp_4 = vec2(600.0000, 400.0000);
vec2 temp_2 = temp_3 / temp_4;
vec2 temp_1 = temp_2 + 0.5000;
Vertex finalReturnValue;
finalReturnValue.position = temp_0;
finalReturnValue.normal = inputs.normal;
finalReturnValue.texCoord = temp_1;
finalReturnValue.color = inputs.color;
return finalReturnValue;
}
Here we can see that it created the modified position (temp0) before using it as an input to the texture coordinates in temp_3.
If I don't assign to a single component of the vector, it works as expected:
textureMaterial = baseMaterialShader().modify(() => {
const t = uniformFloat(() => millis());
getWorldInputs((inputs) => {
let size = [600, 400];
inputs.texCoord = inputs.position.xy / size + 0.5;
inputs.position = [
inputs.position.x,
inputs.position.y + 50 * sin(t * 0.001 + inputs.position.x * 0.01),
inputs.position.z
];
return inputs;
});
});
Vertex getWorldInputs(Vertex inputs) {
vec2 temp_2 = vec2(inputs.position.x, inputs.position.y);
vec2 temp_3 = vec2(600.0000, 400.0000);
vec2 temp_1 = temp_2 / temp_3;
vec2 temp_0 = temp_1 + 0.5000;
Vertex finalReturnValue;
finalReturnValue.position = vec3(inputs.position.x, inputs.position.y + (50.0000 * sin((t * 0.0010) + (inputs.position.x * 0.0100))), inputs.position.z);
finalReturnValue.normal = inputs.normal;
finalReturnValue.texCoord = temp_0;
finalReturnValue.color = inputs.color;
return finalReturnValue;
}
I think the issue with assigning to a component is that it currently modifies the original node that ComponentNodes reference: https://github.com/processing/p5.js/blob/3eae27613378af782910034211bc328cdaa8f125/src/webgl/ShaderGenerator.js#L282-L290
Instead, we might need to make setting a component replace the whole original node so that references to components from before the modification stay unchanged, and new references after the modification have access to a different, changed value.
Some more thoughts: If we replace the original node, then we'd likely have to rewrite statements like this:
// Before
inputs.position.y += sin(t);
// After
inputs.position = inputs.position.set('y', inputs.position.y + sin(t)); // set() returns a new node
Another approach could be to make it so that component nodes own their own value, so that earlier references to inputs.position.y aren't affected by later assignments, as it would replace the y component with a new, different node object.