From 7b3e2a9e9bf799225976686558d65d68d8f7bfaa Mon Sep 17 00:00:00 2001 From: aashu2006 Date: Thu, 11 Jun 2026 23:44:57 +0530 Subject: [PATCH 1/2] Add instanceIndex alias for instanceID --- src/strands/p5.strands.js | 15 +++++++++++++++ src/strands/strands_api.js | 1 + test/types/strands.ts | 1 + test/unit/webgl/p5.Shader.js | 10 ++++++++++ 4 files changed, 27 insertions(+) diff --git a/src/strands/p5.strands.js b/src/strands/p5.strands.js index e9d4cd7143..d53d7fa4f7 100644 --- a/src/strands/p5.strands.js +++ b/src/strands/p5.strands.js @@ -455,6 +455,21 @@ if (typeof p5 !== "undefined") { * @returns {*} The index of the current instance. */ +/** + * @method instanceIndex + * @beta + * @description + * An alias for `instanceID()`. Returns the index + * of the current instance when drawing multiple copies of a shape with + * `model(count)`. + * + * The first instance has an index of `0`, the second has `1`, and so on. + * + * `instanceIndex()` can only be used inside a p5.strands shader callback. + * + * @returns {*} The index of the current instance. + */ + /** * @method smoothstep * @beta diff --git a/src/strands/strands_api.js b/src/strands/strands_api.js index ff5655d81d..bd066fe0fe 100644 --- a/src/strands/strands_api.js +++ b/src/strands/strands_api.js @@ -246,6 +246,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, strandsContext.backend.instanceIdReference()); return createStrandsNode(node.id, node.dimension, strandsContext); }); + augmentFn(fn, p5, 'instanceIndex', fn.instanceID); // Internal methods use p5 static methods; user-facing methods use fn. // Some methods need to be used by both. p5.strandsIf = function(conditionNode, ifBody) { diff --git a/test/types/strands.ts b/test/types/strands.ts index c9af27c05a..726fd57de4 100644 --- a/test/types/strands.ts +++ b/test/types/strands.ts @@ -42,6 +42,7 @@ function starShaderCallback() { function semiSphere() { let id = instanceID(); + let idx = instanceIndex(); let theta = rand2([id, 0.1234]) * TWO_PI + time / 100000; let phi = rand2([id, 3.321]) * PI + time / 50000; diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 0317cf2d24..09b2b3dcd8 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -532,6 +532,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca assert.strictEqual(w, myp5.width); }); +test('instanceIndex() is an alias for instanceID()', () => { + myp5.createCanvas(5, 5, myp5.WEBGL); + myp5.baseMaterialShader().modify(() => { + myp5.getWorldInputs(inputs => { + const idx = myp5.instanceIndex(); + assert.isTrue(idx.isStrandsNode); + return inputs; + }); + }, { myp5 }); +}); test('map() works inside a strands modify callback', () => { myp5.createCanvas(50, 50, myp5.WEBGL); const testShader = myp5.baseMaterialShader().modify(() => { From 6a8e34fec7e0053ce7838d48f345c0a8557e0ebb Mon Sep 17 00:00:00 2001 From: aashu2006 Date: Fri, 12 Jun 2026 23:02:03 +0530 Subject: [PATCH 2/2] Expose instanceIndex as a property --- src/strands/p5.strands.js | 33 +++++++++++++-------------- src/strands/strands_api.js | 39 +++++++++++++++++++++++++++++++- test/unit/visual/cases/webgl.js | 14 ++++++------ test/unit/visual/cases/webgpu.js | 18 +++++++-------- test/unit/webgl/p5.Shader.js | 10 ++++++-- 5 files changed, 78 insertions(+), 36 deletions(-) diff --git a/src/strands/p5.strands.js b/src/strands/p5.strands.js index d53d7fa4f7..d783c77f0a 100644 --- a/src/strands/p5.strands.js +++ b/src/strands/p5.strands.js @@ -338,18 +338,20 @@ if (typeof p5 !== "undefined") { */ /** - * @method instanceID + * @property instanceIndex * @beta * @description * Returns the index of the current instance when drawing multiple copies of a * shape with `model(count)`. The first instance has an - * ID of `0`, the second has `1`, and so on. + * index of `0`, the second has `1`, and so on. * * This lets each copy of a shape behave differently. For example, you can use - * the ID to place instances at different positions, give them different colors, + * the index to place instances at different positions, give them different colors, * or animate them at different speeds. * - * `instanceID()` can only be used inside a p5.strands shader callback. + * `instanceIndex` can only be used inside a p5.strands shader callback. + * + * (Note: `instanceID()` is also available as a function for compatibility.) * * ```js example * let instancesShader; @@ -372,7 +374,7 @@ if (typeof p5 !== "undefined") { * // Spread spheres evenly across the canvas based on their index * let spacing = width / count; * worldInputs.position.x += - * (instanceID() - (count - 1) / 2) * spacing; + * (instanceIndex - (count - 1) / 2) * spacing; * worldInputs.end(); * } * @@ -386,7 +388,7 @@ if (typeof p5 !== "undefined") { * } * ``` * - * If you are using WebGPU mode, a common pattern is to use `instanceID()` to look up data made with + * If you are using WebGPU mode, a common pattern is to use `instanceIndex` to look up data made with * `createStorage()`. * This lets you give each instance different properties. * @@ -429,7 +431,7 @@ if (typeof p5 !== "undefined") { * let itemColor = sharedVec4(); * * worldInputs.begin(); - * let item = data[instanceID()]; + * let item = data[instanceIndex]; * itemColor = item.color; * worldInputs.position += item.position; * worldInputs.end(); @@ -451,23 +453,20 @@ if (typeof p5 !== "undefined") { * This can be paired with `buildComputeShader` * to update the data being read. * - * @webgpu - * @returns {*} The index of the current instance. + * @type {StrandsNode} */ /** - * @method instanceIndex + * @method instanceID * @beta * @description - * An alias for `instanceID()`. Returns the index - * of the current instance when drawing multiple copies of a shape with - * `model(count)`. - * - * The first instance has an index of `0`, the second has `1`, and so on. + * A function alias for `instanceIndex`, kept for compatibility. + * Returns the index of the current instance when drawing multiple copies of a + * shape with `model(count)`. * - * `instanceIndex()` can only be used inside a p5.strands shader callback. + * `instanceID()` can only be used inside a p5.strands shader callback. * - * @returns {*} The index of the current instance. + * @returns {StrandsNode} The index of the current instance. */ /** diff --git a/src/strands/strands_api.js b/src/strands/strands_api.js index bd066fe0fe..d0e6d95acb 100644 --- a/src/strands/strands_api.js +++ b/src/strands/strands_api.js @@ -176,6 +176,43 @@ function installBuiltinGlobalAccessors(strandsContext) { strandsContext._builtinGlobalsAccessorsInstalled = true } +function installInstanceIndexAccessor(strandsContext) { + if (strandsContext._instanceIndexAccessorInstalled) return; + + const getRuntimeP5Instance = () => strandsContext.renderer?._pInst || strandsContext.p5?.instance; + + const instanceIndexGetter = function() { + if (strandsContext.active) { + const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, strandsContext.backend.instanceIdReference()); + return createStrandsNode(node.id, node.dimension, strandsContext); + } + return undefined; + }; + + const inst = getRuntimeP5Instance(); + if (inst?._isGlobal) { + Object.defineProperty(window, 'instanceIndex', { + get: instanceIndexGetter, + configurable: true, + }); + } + + Object.defineProperty(strandsContext.p5.prototype, 'instanceIndex', { + get: instanceIndexGetter, + configurable: true, + }); + + const GraphicsProto = strandsContext.p5?.Graphics?.prototype; + if (GraphicsProto) { + Object.defineProperty(GraphicsProto, 'instanceIndex', { + get: instanceIndexGetter, + configurable: true, + }); + } + + strandsContext._instanceIndexAccessorInstalled = true; +} + ////////////////////////////////////////////// // Prototype mirroring helpers ////////////////////////////////////////////// @@ -246,7 +283,6 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, strandsContext.backend.instanceIdReference()); return createStrandsNode(node.id, node.dimension, strandsContext); }); - augmentFn(fn, p5, 'instanceIndex', fn.instanceID); // Internal methods use p5 static methods; user-facing methods use fn. // Some methods need to be used by both. p5.strandsIf = function(conditionNode, ifBody) { @@ -824,6 +860,7 @@ function enforceReturnTypeMatch(strandsContext, expectedType, returned, hookName } export function createShaderHooksFunctions(strandsContext, fn, shader) { installBuiltinGlobalAccessors(strandsContext) + installInstanceIndexAccessor(strandsContext) // Add shader context to hooks before spreading const vertexHooksWithContext = Object.fromEntries( diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 5d6b19e609..40dd436481 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -972,7 +972,7 @@ visualSuite('WebGL', function() { const sh = p5.baseMaterialShader().modify(() => { const data = p5.uniformTexture(() => positionData); p5.getWorldInputs((inputs) => { - const angle = p5.getTexture(data, [p5.instanceID()/3, 0]).r * p5.TWO_PI; + const angle = p5.getTexture(data, [p5.instanceIndex/3, 0]).r * p5.TWO_PI; inputs.position.xy += [p5.cos(angle) * 10, p5.sin(angle) * 10]; return inputs; }); @@ -1031,7 +1031,7 @@ visualSuite('WebGL', function() { p5.createCanvas(50, 50, p5.WEBGL); const shader = p5.baseMaterialShader().modify(() => { p5.getWorldInputs((inputs) => { - const id = p5.instanceID(); + const id = p5.instanceIndex; const gridSize = 5; const row = p5.floor(id / gridSize); const col = id - row * gridSize; @@ -1055,7 +1055,7 @@ visualSuite('WebGL', function() { p5.createCanvas(50, 50, p5.WEBGL); const shader = p5.baseMaterialShader().modify(() => { p5.getWorldInputs((inputs) => { - const id = p5.instanceID(); + const id = p5.instanceIndex; const gridSize = 5; const row = p5.int(id / gridSize); const col = id - row * gridSize; @@ -1080,7 +1080,7 @@ visualSuite('WebGL', function() { const shader = p5.baseMaterialShader().modify(() => { // Vertex hook: position instances in a horizontal row p5.getWorldInputs((inputs) => { - const id = p5.instanceID(); + const id = p5.instanceIndex; const spacing = 12; const offset = (id - (numInstances - 1) / 2.0) * spacing; inputs.position.x += offset; @@ -1088,7 +1088,7 @@ visualSuite('WebGL', function() { }); // Fragment hook: color each instance based on instanceID p5.getFinalColor((color) => { - const id = p5.instanceID(); + const id = p5.instanceIndex; const t = id / (numInstances - 1.0); color = [t, t, t, 1]; return color; @@ -1359,7 +1359,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre } function semiSphere() { - let id = p5.instanceID(); + let id = p5.instanceIndex; let theta = rand2([id, 0.1234]) * p5.TWO_PI + time / 100000; let phi = rand2([id, 3.321]) * p5.PI + time / 50000; @@ -1377,7 +1377,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre }); p5.getObjectInputs((inputs) => { - let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceID()); + let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceIndex); inputs.position *= size; return inputs; }); diff --git a/test/unit/visual/cases/webgpu.js b/test/unit/visual/cases/webgpu.js index ee9679f45c..0e84359858 100644 --- a/test/unit/visual/cases/webgpu.js +++ b/test/unit/visual/cases/webgpu.js @@ -120,7 +120,7 @@ visualSuite("WebGPU", function () { const model = p5.buildGeometry(() => p5.sphere(5)); const shader = p5.baseMaterialShader().modify(() => { p5.getWorldInputs((inputs) => { - inputs.position += (p5.instanceID() - 1) * 15 + inputs.position += (p5.instanceIndex - 1) * 15 return inputs; }); }, { p5 }); @@ -144,7 +144,7 @@ visualSuite("WebGPU", function () { } function semiSphere() { - let id = p5.instanceID(); + let id = p5.instanceIndex; let theta = rand2([id, 0.1234]) * p5.TWO_PI + time / 100000; let phi = rand2([id, 3.321]) * p5.PI + time / 50000; @@ -162,7 +162,7 @@ visualSuite("WebGPU", function () { }); p5.getObjectInputs((inputs) => { - let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceID()); + let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceIndex); inputs.position *= size; return inputs; }); @@ -304,7 +304,7 @@ visualSuite("WebGPU", function () { const shader = p5.baseMaterialShader().modify(() => { // Vertex hook: position instances in a horizontal row p5.getWorldInputs((inputs) => { - const id = p5.instanceID(); + const id = p5.instanceIndex; const spacing = 12; const offset = (id - (numInstances - 1) / 2.0) * spacing; inputs.position.x += offset; @@ -312,7 +312,7 @@ visualSuite("WebGPU", function () { }); // Fragment hook: color each instance based on instanceID p5.getFinalColor((color) => { - const id = p5.instanceID(); + const id = p5.instanceIndex; const t = id / (numInstances - 1.0); color = [t, t, t, 1]; return color; @@ -1177,7 +1177,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', const sphereShader = p5.baseMaterialShader().modify(() => { const posData = p5.uniformStorage(); p5.getWorldInputs((inputs) => { - const idx = p5.instanceID(); + const idx = p5.instanceIndex; inputs.position.x += posData[idx * 2]; inputs.position.y += posData[idx * 2 + 1]; return inputs; @@ -1285,7 +1285,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', const sphereShader = p5.baseMaterialShader().modify(() => { const buf = p5.uniformStorage('buf', particles); p5.getWorldInputs((inputs) => { - const p = buf[p5.instanceID()].position; + const p = buf[p5.instanceIndex].position; inputs.position.x += p.x; inputs.position.y += p.y; return inputs; @@ -1318,7 +1318,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', const sphereShader = p5.baseMaterialShader().modify(() => { const buf = p5.uniformStorage('buf', particles); p5.getWorldInputs((inputs) => { - const p = buf[p5.instanceID()].position; + const p = buf[p5.instanceIndex].position; inputs.position.x += p.x; inputs.position.y += p.y; return inputs; @@ -1351,7 +1351,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', const sphereShader = p5.baseMaterialShader().modify(() => { const buf = p5.uniformStorage('buf', { position: [0, 0] }); p5.getWorldInputs((inputs) => { - const p = buf[p5.instanceID()].position; + const p = buf[p5.instanceIndex].position; inputs.position.x += p.x; inputs.position.y += p.y; return inputs; diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 09b2b3dcd8..b944b5fced 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -532,12 +532,18 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca assert.strictEqual(w, myp5.width); }); -test('instanceIndex() is an alias for instanceID()', () => { +test('instanceIndex is a value and instanceID() is a compatibility alias', () => { myp5.createCanvas(5, 5, myp5.WEBGL); myp5.baseMaterialShader().modify(() => { myp5.getWorldInputs(inputs => { - const idx = myp5.instanceIndex(); + // instanceIndex is a property — no parentheses + const idx = myp5.instanceIndex; assert.isTrue(idx.isStrandsNode); + + // instanceID() is a function kept for compatibility + const idxCompat = myp5.instanceID(); + assert.isTrue(idxCompat.isStrandsNode); + return inputs; }); }, { myp5 });