Skip to content

Commit 84bfad4

Browse files
authored
Merge branch 'main' into fix/hook-object-returns
2 parents 4604181 + 71d062a commit 84bfad4

7 files changed

Lines changed: 104 additions & 30 deletions

File tree

src/core/p5.Renderer2D.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -444,10 +444,12 @@ class Renderer2D extends Renderer {
444444
ctx.save();
445445
ctx.clearRect(0, 0, img.canvas.width, img.canvas.height);
446446

447+
const tint = this.states.tint._getRGBA([255, 255, 255, 255]);
448+
447449
if (
448-
this.states.tint[0] < 255 ||
449-
this.states.tint[1] < 255 ||
450-
this.states.tint[2] < 255
450+
tint[0] < 255 ||
451+
tint[1] < 255 ||
452+
tint[2] < 255
451453
) {
452454
// Color tint: we need to use the multiply blend mode to change the colors.
453455
// However, the canvas implementation of this destroys the alpha channel of
@@ -470,16 +472,16 @@ class Renderer2D extends Renderer {
470472

471473
// Apply color tint
472474
ctx.globalCompositeOperation = 'multiply';
473-
ctx.fillStyle = `rgb(${this.states.tint.slice(0, 3).join(', ')})`;
475+
ctx.fillStyle = `rgb(${tint.slice(0, 3).join(', ')})`;
474476
ctx.fillRect(0, 0, img.canvas.width, img.canvas.height);
475477

476478
// Replace the alpha channel with the original alpha * the alpha tint
477479
ctx.globalCompositeOperation = 'destination-in';
478-
ctx.globalAlpha = this.states.tint[3] / 255;
480+
ctx.globalAlpha = tint[3] / 255;
479481
ctx.drawImage(img.canvas, 0, 0);
480482
} else {
481483
// If we only need to change the alpha, we can skip all the extra work!
482-
ctx.globalAlpha = this.states.tint[3] / 255;
484+
ctx.globalAlpha = tint[3] / 255;
483485
ctx.drawImage(img.canvas, 0, 0);
484486
}
485487

src/strands/p5.strands.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -338,18 +338,20 @@ if (typeof p5 !== "undefined") {
338338
*/
339339

340340
/**
341-
* @method instanceID
341+
* @property instanceIndex
342342
* @beta
343343
* @description
344344
* Returns the index of the current instance when drawing multiple copies of a
345345
* shape with <a href="#/p5/model">`model(count)`</a>. The first instance has an
346-
* ID of `0`, the second has `1`, and so on.
346+
* index of `0`, the second has `1`, and so on.
347347
*
348348
* This lets each copy of a shape behave differently. For example, you can use
349-
* the ID to place instances at different positions, give them different colors,
349+
* the index to place instances at different positions, give them different colors,
350350
* or animate them at different speeds.
351351
*
352-
* `instanceID()` can only be used inside a p5.strands shader callback.
352+
* `instanceIndex` can only be used inside a p5.strands shader callback.
353+
*
354+
* (Note: `instanceID()` is also available as a function for compatibility.)
353355
*
354356
* ```js example
355357
* let instancesShader;
@@ -372,7 +374,7 @@ if (typeof p5 !== "undefined") {
372374
* // Spread spheres evenly across the canvas based on their index
373375
* let spacing = width / count;
374376
* worldInputs.position.x +=
375-
* (instanceID() - (count - 1) / 2) * spacing;
377+
* (instanceIndex - (count - 1) / 2) * spacing;
376378
* worldInputs.end();
377379
* }
378380
*
@@ -386,7 +388,7 @@ if (typeof p5 !== "undefined") {
386388
* }
387389
* ```
388390
*
389-
* If you are using WebGPU mode, a common pattern is to use `instanceID()` to look up data made with
391+
* If you are using WebGPU mode, a common pattern is to use `instanceIndex` to look up data made with
390392
* <a href="#/p5/createStorage">`createStorage()`</a>.
391393
* This lets you give each instance different properties.
392394
*
@@ -429,7 +431,7 @@ if (typeof p5 !== "undefined") {
429431
* let itemColor = sharedVec4();
430432
*
431433
* worldInputs.begin();
432-
* let item = data[instanceID()];
434+
* let item = data[instanceIndex];
433435
* itemColor = item.color;
434436
* worldInputs.position += item.position;
435437
* worldInputs.end();
@@ -451,7 +453,22 @@ if (typeof p5 !== "undefined") {
451453
* This can be paired with <a href="#/p5/buildComputeShader">`buildComputeShader`</a>
452454
* to update the data being read.
453455
*
454-
* @webgpu
456+
* @type {*}
457+
*/
458+
459+
/**
460+
* @method instanceID
461+
* @beta
462+
* @deprecated Use <a href="#/p5/instanceIndex">`instanceIndex`</a> instead.
463+
* @description
464+
* A function alias for <a href="#/p5/instanceIndex">`instanceIndex`</a>, kept for compatibility.
465+
* Prefer using <a href="#/p5/instanceIndex">`instanceIndex`</a> directly as a value instead.
466+
*
467+
* Returns the index of the current instance when drawing multiple copies of a
468+
* shape with <a href="#/p5/model">`model(count)`</a>.
469+
*
470+
* `instanceID()` can only be used inside a p5.strands shader callback.
471+
*
455472
* @returns {*} The index of the current instance.
456473
*/
457474

src/strands/strands_api.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,43 @@ function installBuiltinGlobalAccessors(strandsContext) {
176176
strandsContext._builtinGlobalsAccessorsInstalled = true
177177
}
178178

179+
function installInstanceIndexAccessor(strandsContext) {
180+
if (strandsContext._instanceIndexAccessorInstalled) return;
181+
182+
const getRuntimeP5Instance = () => strandsContext.renderer?._pInst || strandsContext.p5?.instance;
183+
184+
const instanceIndexGetter = function() {
185+
if (strandsContext.active) {
186+
const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, strandsContext.backend.instanceIdReference());
187+
return createStrandsNode(node.id, node.dimension, strandsContext);
188+
}
189+
return undefined;
190+
};
191+
192+
const inst = getRuntimeP5Instance();
193+
if (inst?._isGlobal) {
194+
Object.defineProperty(window, 'instanceIndex', {
195+
get: instanceIndexGetter,
196+
configurable: true,
197+
});
198+
}
199+
200+
Object.defineProperty(strandsContext.p5.prototype, 'instanceIndex', {
201+
get: instanceIndexGetter,
202+
configurable: true,
203+
});
204+
205+
const GraphicsProto = strandsContext.p5?.Graphics?.prototype;
206+
if (GraphicsProto) {
207+
Object.defineProperty(GraphicsProto, 'instanceIndex', {
208+
get: instanceIndexGetter,
209+
configurable: true,
210+
});
211+
}
212+
213+
strandsContext._instanceIndexAccessorInstalled = true;
214+
}
215+
179216
//////////////////////////////////////////////
180217
// Prototype mirroring helpers
181218
//////////////////////////////////////////////
@@ -967,6 +1004,7 @@ function enforceReturnTypeMatch(strandsContext, expectedType, returned, hookName
9671004
}
9681005
export function createShaderHooksFunctions(strandsContext, fn, shader) {
9691006
installBuiltinGlobalAccessors(strandsContext)
1007+
installInstanceIndexAccessor(strandsContext)
9701008

9711009
// Add shader context to hooks before spreading
9721010
const vertexHooksWithContext = Object.fromEntries(

test/types/strands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ function starShaderCallback() {
4242

4343
function semiSphere() {
4444
let id = instanceID();
45+
let idx = instanceIndex;
4546
let theta = rand2([id, 0.1234]) * TWO_PI + time / 100000;
4647
let phi = rand2([id, 3.321]) * PI + time / 50000;
4748

test/unit/visual/cases/webgl.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,7 @@ visualSuite('WebGL', function() {
972972
const sh = p5.baseMaterialShader().modify(() => {
973973
const data = p5.uniformTexture(() => positionData);
974974
p5.getWorldInputs((inputs) => {
975-
const angle = p5.getTexture(data, [p5.instanceID()/3, 0]).r * p5.TWO_PI;
975+
const angle = p5.getTexture(data, [p5.instanceIndex/3, 0]).r * p5.TWO_PI;
976976
inputs.position.xy += [p5.cos(angle) * 10, p5.sin(angle) * 10];
977977
return inputs;
978978
});
@@ -1031,7 +1031,7 @@ visualSuite('WebGL', function() {
10311031
p5.createCanvas(50, 50, p5.WEBGL);
10321032
const shader = p5.baseMaterialShader().modify(() => {
10331033
p5.getWorldInputs((inputs) => {
1034-
const id = p5.instanceID();
1034+
const id = p5.instanceIndex;
10351035
const gridSize = 5;
10361036
const row = p5.floor(id / gridSize);
10371037
const col = id - row * gridSize;
@@ -1055,7 +1055,7 @@ visualSuite('WebGL', function() {
10551055
p5.createCanvas(50, 50, p5.WEBGL);
10561056
const shader = p5.baseMaterialShader().modify(() => {
10571057
p5.getWorldInputs((inputs) => {
1058-
const id = p5.instanceID();
1058+
const id = p5.instanceIndex;
10591059
const gridSize = 5;
10601060
const row = p5.int(id / gridSize);
10611061
const col = id - row * gridSize;
@@ -1080,15 +1080,15 @@ visualSuite('WebGL', function() {
10801080
const shader = p5.baseMaterialShader().modify(() => {
10811081
// Vertex hook: position instances in a horizontal row
10821082
p5.getWorldInputs((inputs) => {
1083-
const id = p5.instanceID();
1083+
const id = p5.instanceIndex;
10841084
const spacing = 12;
10851085
const offset = (id - (numInstances - 1) / 2.0) * spacing;
10861086
inputs.position.x += offset;
10871087
return inputs;
10881088
});
10891089
// Fragment hook: color each instance based on instanceID
10901090
p5.getFinalColor((color) => {
1091-
const id = p5.instanceID();
1091+
const id = p5.instanceIndex;
10921092
const t = id / (numInstances - 1.0);
10931093
color = [t, t, t, 1];
10941094
return color;
@@ -1359,7 +1359,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre
13591359
}
13601360

13611361
function semiSphere() {
1362-
let id = p5.instanceID();
1362+
let id = p5.instanceIndex;
13631363
let theta = rand2([id, 0.1234]) * p5.TWO_PI + time / 100000;
13641364
let phi = rand2([id, 3.321]) * p5.PI + time / 50000;
13651365

@@ -1377,7 +1377,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre
13771377
});
13781378

13791379
p5.getObjectInputs((inputs) => {
1380-
let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceID());
1380+
let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceIndex);
13811381
inputs.position *= size;
13821382
return inputs;
13831383
});

test/unit/visual/cases/webgpu.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ visualSuite("WebGPU", function () {
120120
const model = p5.buildGeometry(() => p5.sphere(5));
121121
const shader = p5.baseMaterialShader().modify(() => {
122122
p5.getWorldInputs((inputs) => {
123-
inputs.position += (p5.instanceID() - 1) * 15
123+
inputs.position += (p5.instanceIndex - 1) * 15
124124
return inputs;
125125
});
126126
}, { p5 });
@@ -144,7 +144,7 @@ visualSuite("WebGPU", function () {
144144
}
145145

146146
function semiSphere() {
147-
let id = p5.instanceID();
147+
let id = p5.instanceIndex;
148148
let theta = rand2([id, 0.1234]) * p5.TWO_PI + time / 100000;
149149
let phi = rand2([id, 3.321]) * p5.PI + time / 50000;
150150

@@ -162,7 +162,7 @@ visualSuite("WebGPU", function () {
162162
});
163163

164164
p5.getObjectInputs((inputs) => {
165-
let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceID());
165+
let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceIndex);
166166
inputs.position *= size;
167167
return inputs;
168168
});
@@ -304,15 +304,15 @@ visualSuite("WebGPU", function () {
304304
const shader = p5.baseMaterialShader().modify(() => {
305305
// Vertex hook: position instances in a horizontal row
306306
p5.getWorldInputs((inputs) => {
307-
const id = p5.instanceID();
307+
const id = p5.instanceIndex;
308308
const spacing = 12;
309309
const offset = (id - (numInstances - 1) / 2.0) * spacing;
310310
inputs.position.x += offset;
311311
return inputs;
312312
});
313313
// Fragment hook: color each instance based on instanceID
314314
p5.getFinalColor((color) => {
315-
const id = p5.instanceID();
315+
const id = p5.instanceIndex;
316316
const t = id / (numInstances - 1.0);
317317
color = [t, t, t, 1];
318318
return color;
@@ -1196,7 +1196,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)',
11961196
const sphereShader = p5.baseMaterialShader().modify(() => {
11971197
const posData = p5.uniformStorage();
11981198
p5.getWorldInputs((inputs) => {
1199-
const idx = p5.instanceID();
1199+
const idx = p5.instanceIndex;
12001200
inputs.position.x += posData[idx * 2];
12011201
inputs.position.y += posData[idx * 2 + 1];
12021202
return inputs;
@@ -1304,7 +1304,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)',
13041304
const sphereShader = p5.baseMaterialShader().modify(() => {
13051305
const buf = p5.uniformStorage('buf', particles);
13061306
p5.getWorldInputs((inputs) => {
1307-
const p = buf[p5.instanceID()].position;
1307+
const p = buf[p5.instanceIndex].position;
13081308
inputs.position.x += p.x;
13091309
inputs.position.y += p.y;
13101310
return inputs;
@@ -1337,7 +1337,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)',
13371337
const sphereShader = p5.baseMaterialShader().modify(() => {
13381338
const buf = p5.uniformStorage('buf', particles);
13391339
p5.getWorldInputs((inputs) => {
1340-
const p = buf[p5.instanceID()].position;
1340+
const p = buf[p5.instanceIndex].position;
13411341
inputs.position.x += p.x;
13421342
inputs.position.y += p.y;
13431343
return inputs;
@@ -1370,7 +1370,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)',
13701370
const sphereShader = p5.baseMaterialShader().modify(() => {
13711371
const buf = p5.uniformStorage('buf', { position: [0, 0] });
13721372
p5.getWorldInputs((inputs) => {
1373-
const p = buf[p5.instanceID()].position;
1373+
const p = buf[p5.instanceIndex].position;
13741374
inputs.position.x += p.x;
13751375
inputs.position.y += p.y;
13761376
return inputs;

test/unit/webgl/p5.Shader.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,22 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca
541541
assert.strictEqual(w, myp5.width);
542542
});
543543

544+
test('instanceIndex is a value and instanceID() is a compatibility alias', () => {
545+
myp5.createCanvas(5, 5, myp5.WEBGL);
546+
myp5.baseMaterialShader().modify(() => {
547+
myp5.getWorldInputs(inputs => {
548+
// instanceIndex is a property — no parentheses
549+
const idx = myp5.instanceIndex;
550+
assert.isTrue(idx.isStrandsNode);
551+
552+
// instanceID() is a function kept for compatibility
553+
const idxCompat = myp5.instanceID();
554+
assert.isTrue(idxCompat.isStrandsNode);
555+
556+
return inputs;
557+
});
558+
}, { myp5 });
559+
});
544560
test('map() works inside a strands modify callback', () => {
545561
myp5.createCanvas(50, 50, myp5.WEBGL);
546562
const testShader = myp5.baseMaterialShader().modify(() => {

0 commit comments

Comments
 (0)