Skip to content

Commit 01e1734

Browse files
authored
Merge pull request #8251 from processing/fix/strands-texture
Fix some bugs in p5.strands type aliasing
2 parents fbad474 + d35f103 commit 01e1734

File tree

8 files changed

+250
-51
lines changed

8 files changed

+250
-51
lines changed

docs/parameterData.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3168,6 +3168,13 @@
31683168
]
31693169
]
31703170
},
3171+
"contrast": {
3172+
"overloads": [
3173+
[
3174+
"Color"
3175+
]
3176+
]
3177+
},
31713178
"setRed": {
31723179
"overloads": [
31733180
[

src/strands/strands_api.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,22 +139,33 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
139139
});
140140
return createStrandsNode(id, dimension, strandsContext);
141141
};
142-
// Next is type constructors and uniform functions
142+
143+
// Next is type constructors and uniform functions.
144+
// For some of them, we have aliases so that you can write either a more human-readable
145+
// variant or also one more directly translated from GLSL, or to be more compatible with
146+
// APIs we documented at the release of 2.x and have to continue supporting.
143147
for (const type in DataType) {
144148
if (type === BaseType.DEFER) {
145149
continue;
146150
}
147151
const typeInfo = DataType[type];
152+
const typeAliases = [];
148153
let pascalTypeName;
149154
if (/^[ib]vec/.test(typeInfo.fnName)) {
150155
pascalTypeName = typeInfo.fnName
151-
.slice(0, 2).toUpperCase()
152-
+ typeInfo.fnName
153-
.slice(2)
154-
.toLowerCase();
156+
.slice(0, 2).toUpperCase()
157+
+ typeInfo.fnName
158+
.slice(2)
159+
.toLowerCase();
160+
typeAliases.push(pascalTypeName.replace('Vec', 'Vector'));
155161
} else {
156162
pascalTypeName = typeInfo.fnName.charAt(0).toUpperCase()
157-
+ typeInfo.fnName.slice(1).toLowerCase();
163+
+ typeInfo.fnName.slice(1);
164+
if (pascalTypeName === 'Sampler2D') {
165+
typeAliases.push('Texture')
166+
} else if (/^vec/.test(typeInfo.fnName)) {
167+
typeAliases.push(pascalTypeName.replace('Vec', 'Vector'));
168+
}
158169
}
159170
fn[`uniform${pascalTypeName}`] = function(name, defaultValue) {
160171
const { id, dimension } = build.variableNode(strandsContext, typeInfo, name);
@@ -183,11 +194,14 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
183194

184195
// Alias varying* as shared* for backward compatibility
185196
fn[`varying${pascalTypeName}`] = fn[`shared${pascalTypeName}`];
186-
if (pascalTypeName.startsWith('Vec')) {
197+
198+
for (const typeAlias of typeAliases) {
199+
console.log(`Aliasing ${typeAlias} to ${pascalTypeName}`)
187200
// For compatibility, also alias uniformVec2 as uniformVector2, what we initially
188201
// documented these as
189-
fn[`uniform${pascalTypeName.replace('Vec', 'Vector')}`] = fn[`uniform${pascalTypeName}`];
190-
fn[`varying${pascalTypeName.replace('Vec', 'Vector')}`] = fn[`varying${pascalTypeName}`];
202+
fn[`uniform${typeAlias}`] = fn[`uniform${pascalTypeName}`];
203+
fn[`varying${typeAlias}`] = fn[`varying${pascalTypeName}`];
204+
fn[`shared${typeAlias}`] = fn[`shared${pascalTypeName}`];
191205
}
192206
const originalp5Fn = fn[typeInfo.fnName];
193207
fn[typeInfo.fnName] = function(...args) {

test/types/strands.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Based on p5.strands: Introduction to Shaders By Luke Plowden
2+
// https://beta.p5js.org/tutorials/intro-to-p5-strands/
3+
4+
import '../../types/global'
5+
6+
let starShader: p5.Shader;
7+
let starStrokeShader: p5.Shader;
8+
let stars: p5.Geometry;
9+
let originalImage: p5.Framebuffer;
10+
let pixelateShader: p5.Shader;
11+
let fresnelShader: p5.Shader;
12+
let bloomShader: p5.Shader;
13+
14+
function fresnelShaderCallback() {
15+
const fresnelPower = uniformFloat(2);
16+
const fresnelBias = uniformFloat(-0.1);
17+
const fresnelScale = uniformFloat(2);
18+
19+
getCameraInputs((inputs) => {
20+
let n = normalize(inputs.normal);
21+
let v = normalize(-inputs.position);
22+
let base = 1.0 - dot(n, v);
23+
let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias;
24+
let col = mix([0, 0, 0], [1, .5, .7], fresnel);
25+
inputs.color = [col, 1];
26+
return inputs;
27+
});
28+
}
29+
30+
function starShaderCallback() {
31+
const time = uniformFloat(() => millis());
32+
const skyRadius = uniformFloat(250);
33+
const testVec = uniformVector2(() => 123);
34+
const testSharedVec = sharedVec2();
35+
const testSharedVector = sharedVector2();
36+
const testVaryingVec = varyingVec2();
37+
const testVaryingVector = varyingVector2();
38+
39+
function rand2(st) {
40+
return fract(sin(dot(st, [12.9898, 78.233])) * 43758.5453123);
41+
}
42+
43+
function semiSphere() {
44+
let id = instanceID();
45+
let theta = rand2([id, 0.1234]) * TWO_PI + time / 100000;
46+
let phi = rand2([id, 3.321]) * PI + time / 50000;
47+
48+
let r = skyRadius;
49+
r *= sin(phi);
50+
let x = r * sin(phi) * cos(theta);
51+
let y = r * 1.5 * cos(phi);
52+
let z = r * sin(phi) * sin(theta);
53+
return [x, y, z];
54+
}
55+
56+
getWorldInputs((inputs) => {
57+
inputs.position += semiSphere();
58+
return inputs;
59+
});
60+
61+
getObjectInputs((inputs) => {
62+
let size = 1 + 0.5 * sin(time * 0.002 + instanceID());
63+
inputs.position *= size;
64+
return inputs;
65+
});
66+
}
67+
68+
function pixelateShaderCallback() {
69+
const pixelCountX = uniformFloat(()=> 280);
70+
71+
getColor((inputs, canvasContent) => {
72+
const aspectRatio = inputs.canvasSize.x / inputs.canvasSize.y;
73+
const pixelSize = [pixelCountX, pixelCountX / aspectRatio];
74+
75+
let coord = inputs.texCoord;
76+
// @ts-ignore
77+
coord = floor(coord * pixelSize) / pixelSize;
78+
79+
let col = getTexture(canvasContent, coord);
80+
return col//[coord, 0, 1];
81+
});
82+
}
83+
84+
function bloomShaderCallback() {
85+
const preBlur = uniformSampler2D(() => originalImage);
86+
87+
getColor((input, canvasContent) => {
88+
const blurredCol = getTexture(canvasContent, input.texCoord);
89+
const originalCol = getTexture(preBlur, input.texCoord);
90+
91+
const intensity = max(originalCol, 0.1) * 12.2;
92+
93+
const bloom = originalCol + blurredCol * intensity;
94+
return [bloom.rgb, 1];
95+
});
96+
}
97+
98+
async function setup(){
99+
createCanvas(800, 600, WEBGL);
100+
pixelDensity(1);
101+
stars = buildGeometry(() => sphere(8, 4, 2))
102+
originalImage = createFramebuffer();
103+
104+
starShader = baseMaterialShader().modify(starShaderCallback);
105+
starStrokeShader = baseStrokeShader().modify(starShaderCallback)
106+
fresnelShader = baseColorShader().modify(fresnelShaderCallback);
107+
bloomShader = baseFilterShader().modify(bloomShaderCallback);
108+
pixelateShader = baseFilterShader().modify(pixelateShaderCallback);
109+
}
110+
111+
function draw(){
112+
originalImage.begin();
113+
background(0);
114+
orbitControl();
115+
116+
push()
117+
strokeWeight(2)
118+
stroke(255,0,0)
119+
rotateX(PI/2 + millis() * 0.0005);
120+
fill(255,100, 150)
121+
strokeShader(starStrokeShader)
122+
shader(starShader);
123+
model(stars, 1000);
124+
pop()
125+
126+
push()
127+
shader(fresnelShader)
128+
noStroke()
129+
sphere(90);
130+
filter(pixelateShader);
131+
pop()
132+
133+
originalImage.end();
134+
135+
imageMode(CENTER)
136+
image(originalImage, 0, 0)
137+
138+
filter(BLUR, 15)
139+
filter(bloomShader);
140+
}

test/unit/visual/cases/webgl.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,6 @@ visualSuite('WebGL', function() {
679679
p5.createCanvas(50, 50, p5.WEBGL);
680680
const tex = await p5.loadImage('/unit/assets/cat.jpg');
681681
p5.texture(tex);
682-
p5.texture(tex);
683682
p5.rect(-20, -20, 40, 40);
684683
screenshot();
685684
});
@@ -688,9 +687,25 @@ visualSuite('WebGL', function() {
688687
p5.createCanvas(50, 50, p5.WEBGL);
689688
const tex = await p5.loadImage('/unit/assets/cat.jpg');
690689
p5.texture(tex);
691-
p5.texture(tex);
692690
p5.rect(-20, -20, 40, 40, 10);
693691
screenshot();
694692
});
695693
});
694+
695+
visualSuite('textures in p5.strands', async () => {
696+
visualTest('uniformTexture() works', async (p5, screenshot) => {
697+
p5.createCanvas(50, 50, p5.WEBGL);
698+
const tex = await p5.loadImage('/unit/assets/cat.jpg');
699+
const shader = p5.baseMaterialShader().modify(() => {
700+
const texUniform = p5.uniformTexture(() => tex)
701+
p5.getPixelInputs((inputs) => {
702+
inputs.color = p5.getTexture(texUniform, inputs.texCoord);
703+
return inputs;
704+
});
705+
}, { p5, tex });
706+
p5.shader(shader);
707+
p5.rect(-20, -20, 40, 40);
708+
screenshot();
709+
});
710+
});
696711
});
5.17 KB
Loading
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"numScreenshots": 1
3+
}

test/unit/webgl/p5.Shader.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@ suite('p5.Shader', function() {
543543
myp5.createCanvas(100, 50, myp5.WEBGL);
544544
const testShader = myp5.baseMaterialShader().modify(() => {
545545
myp5.getPixelInputs(inputs => {
546-
debugger
547546
const uv = inputs.texCoord;
548547
const condition = uv.x > 0.5; // left half false, right half true
549548
let color = myp5.float(0.0);

utils/typescript.mjs

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,27 @@ allRawData.forEach(entry => {
3636

3737
// Process strands functions to extract p5 methods
3838
function processStrandsFunctions() {
39-
const strandsMethods = [];
39+
const strandsMethods = [
40+
{
41+
name: 'instanceID',
42+
overloads: [{
43+
params: [],
44+
return: {
45+
type: { type: 'NameExpression', name: 'any' } // Return 'any' for strands nodes
46+
}
47+
}],
48+
description: `Returns the ID when drawing many instances`,
49+
static: false
50+
},
51+
{
52+
name: 'discard',
53+
overloads: [{
54+
params: [],
55+
}],
56+
description: `Discards the current pixel`,
57+
static: false
58+
},
59+
];
4060

4161
// Add ALL GLSL builtin functions (both isp5Function: true and false)
4262
for (const [functionName, overloads] of Object.entries(builtInGLSLFunctions)) {
@@ -61,62 +81,63 @@ function processStrandsFunctions() {
6181
}
6282

6383
// Add uniform functions: uniformFloat, uniformVec2, etc.
64-
const uniformMethods = [];
84+
const typeMethods = [];
6585
for (const type in DataType) {
6686
if (type === 'defer') {
6787
continue;
6888
}
6989

7090
const typeInfo = DataType[type];
7191
let pascalTypeName;
92+
const typeAliases = [];
7293

7394
if (/^[ib]vec/.test(typeInfo.fnName)) {
7495
pascalTypeName = typeInfo.fnName
7596
.slice(0, 2).toUpperCase()
7697
+ typeInfo.fnName
7798
.slice(2)
7899
.toLowerCase();
100+
typeAliases.push(pascalTypeName.replace('Vec', 'Vector'));
79101
} else {
80102
pascalTypeName = typeInfo.fnName.charAt(0).toUpperCase()
81-
+ typeInfo.fnName.slice(1).toLowerCase();
103+
+ typeInfo.fnName.slice(1);
104+
if (pascalTypeName === 'Sampler2D') {
105+
typeAliases.push('Texture')
106+
} else if (/^vec/.test(typeInfo.fnName)) {
107+
typeAliases.push(pascalTypeName.replace('Vec', 'Vector'));
108+
}
82109
}
83110

84-
const uniformMethodName = `uniform${pascalTypeName}`;
85-
const uniformMethod = {
86-
name: uniformMethodName,
87-
overloads: [{
88-
params: [
89-
{
90-
name: 'name',
91-
type: { type: 'NameExpression', name: 'String' },
92-
optional: false
93-
},
94-
{
95-
name: 'defaultValue',
96-
type: { type: 'NameExpression', name: 'any' },
97-
optional: true
111+
typeMethods.push(...[pascalTypeName, ...typeAliases].flatMap((typeName) => [
112+
{
113+
name: `uniform${typeName}`,
114+
overloads: [{
115+
params: [
116+
{
117+
name: 'defaultValue',
118+
type: { type: 'NameExpression', name: 'any' },
119+
optional: true
120+
}
121+
],
122+
return: {
123+
type: { type: 'NameExpression', name: 'any' }
98124
}
99-
],
100-
return: {
101-
type: { type: 'NameExpression', name: 'any' }
102-
}
103-
}],
104-
description: `Create a ${pascalTypeName} uniform variable`,
105-
static: false
106-
};
107-
108-
uniformMethods.push(uniformMethod);
109-
110-
// Add Vector aliases for Vec types
111-
if (pascalTypeName.startsWith('Vec')) {
112-
const vectorMethodName = `uniform${pascalTypeName.replace('Vec', 'Vector')}`;
113-
const vectorMethod = {
114-
...uniformMethod,
115-
name: vectorMethodName,
116-
description: `Create a ${pascalTypeName.replace('Vec', 'Vector')} uniform variable`
117-
};
118-
uniformMethods.push(vectorMethod);
119-
}
125+
}],
126+
description: `Create a ${pascalTypeName} uniform variable`,
127+
static: false
128+
},
129+
...['varying', 'shared'].map((prefix) => ({
130+
name: `${prefix}${typeName}`,
131+
overloads: [{
132+
params: [],
133+
return: {
134+
type: { type: 'NameExpression', name: 'any' }
135+
}
136+
}],
137+
description: `Create a shared ${pascalTypeName} to pass data between hooks`,
138+
static: false
139+
}))
140+
]));
120141
}
121142

122143
// Add type casting functions (DataType constructor functions)
@@ -148,7 +169,7 @@ function processStrandsFunctions() {
148169
typeCastingMethods.push(castingMethod);
149170
}
150171

151-
return [...strandsMethods, ...uniformMethods, ...typeCastingMethods];
172+
return [...strandsMethods, ...typeMethods, ...typeCastingMethods];
152173
}
153174

154175
// TypeScript-specific type conversion from raw type objects

0 commit comments

Comments
 (0)