diff --git a/src/strands/p5.strands.js b/src/strands/p5.strands.js
index e9d4cd7143..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,8 +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 instanceID
+ * @beta
+ * @description
+ * 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)`.
+ *
+ * `instanceID()` can only be used inside a p5.strands shader callback.
+ *
+ * @returns {StrandsNode} The index of the current instance.
*/
/**
diff --git a/src/strands/strands_api.js b/src/strands/strands_api.js
index 85af78f3c7..4294958cdb 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
//////////////////////////////////////////////
@@ -956,6 +993,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/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/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 6434642c1d..b5f97cb6bc 100644
--- a/test/unit/webgl/p5.Shader.js
+++ b/test/unit/webgl/p5.Shader.js
@@ -532,6 +532,22 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca
assert.strictEqual(w, myp5.width);
});
+test('instanceIndex is a value and instanceID() is a compatibility alias', () => {
+ myp5.createCanvas(5, 5, myp5.WEBGL);
+ myp5.baseMaterialShader().modify(() => {
+ myp5.getWorldInputs(inputs => {
+ // 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 });
+});
test('map() works inside a strands modify callback', () => {
myp5.createCanvas(50, 50, myp5.WEBGL);
const testShader = myp5.baseMaterialShader().modify(() => {