Skip to content

Commit 83e3a1d

Browse files
committed
Address review feedback for instances API
1 parent 8d70be1 commit 83e3a1d

5 files changed

Lines changed: 95 additions & 26 deletions

File tree

src/core/p5.Renderer3D.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -549,12 +549,8 @@ export class Renderer3D extends Renderer {
549549
this.updateShapeVertexProperties();
550550
}
551551

552-
model(model, count = 1) {
553-
// Use _instanceCount only when count was NOT explicitly passed.
554-
// arguments.length distinguishes model(geom) from model(geom, 1).
555-
if (arguments.length < 2 && this._instanceCount !== undefined) {
556-
count = this._instanceCount;
557-
}
552+
model(model, count) {
553+
count = count ?? this._instanceCount ?? 1;
558554
if (model.vertices.length > 0) {
559555
if (this.geometryBuilder) {
560556
this.geometryBuilder.addRetained(model);

src/webgl/3d_primitives.js

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2596,6 +2596,18 @@ function primitives3D(p5, fn){
25962596
return this._renderer.curveDetail(d);
25972597
};
25982598

2599+
/**
2600+
* @typedef {Object} InstancesWrapper
2601+
* @property {function(radius: Number=, detailX: Number=, detailY: Number=): undefined} sphere
2602+
* @property {function(width: Number=, height: Number=, depth: Number=, detailX: Number=, detailY: Number=): undefined} box
2603+
* @property {function(width: Number=, height: Number=, detailX: Number=, detailY: Number=): undefined} plane
2604+
* @property {function(radiusX: Number=, radiusY: Number=, radiusZ: Number=, detailX: Number=, detailY: Number=): undefined} ellipsoid
2605+
* @property {function(radius: Number=, height: Number=, detailX: Number=, detailY: Number=, bottomCap: Boolean=, topCap: Boolean=): undefined} cylinder
2606+
* @property {function(radius: Number=, height: Number=, detailX: Number=, detailY: Number=, cap: Boolean=): undefined} cone
2607+
* @property {function(radius: Number=, tubeRadius: Number=, detailX: Number=, detailY: Number=): undefined} torus
2608+
* @property {function(model: p5.Geometry, count: Number=): undefined} model
2609+
*/
2610+
25992611
/**
26002612
* Draws `count` instances of the next 3D primitive or model using WebGL
26012613
* instanced rendering.
@@ -2608,21 +2620,38 @@ function primitives3D(p5, fn){
26082620
* @method instances
26092621
* @param {Number} count number of instances to draw. Must be a positive
26102622
* integer.
2611-
* @returns {Object} an object with methods `sphere`, `box`, `plane`,
2623+
* @returns {p5.InstancesWrapper} an object with methods `sphere`, `box`, `plane`,
26122624
* `ellipsoid`, `cylinder`, `cone`, `torus`, and `model`. Call one of
26132625
* these to draw `count` instances of that primitive.
26142626
*
26152627
* @example
26162628
* <div>
26172629
* <code>
2618-
* // Draw 10 spheres in a single instanced draw call.
2619-
* // A custom shader reads gl_InstanceID to offset each sphere.
2630+
* let myShader;
2631+
* let count = 5;
2632+
*
26202633
* function setup() {
2621-
* createCanvas(100, 100, WEBGL);
2634+
* createCanvas(200, 200, WEBGL);
2635+
* myShader = buildMaterialShader(drawSpaced);
2636+
* describe('Five red spheres arranged in a horizontal line.');
26222637
* }
2638+
*
2639+
* function drawSpaced() {
2640+
* worldInputs.begin();
2641+
* // Spread spheres evenly across the canvas based on their index
2642+
* let spacing = width / count;
2643+
* worldInputs.position.x +=
2644+
* (instanceIndex - (count - 1) / 2) * spacing;
2645+
* worldInputs.end();
2646+
* }
2647+
*
26232648
* function draw() {
2624-
* background(200);
2625-
* instances(10).sphere(20);
2649+
* background(220);
2650+
* lights();
2651+
* noStroke();
2652+
* fill('red');
2653+
* shader(myShader);
2654+
* instances(count).sphere(15);
26262655
* }
26272656
* </code>
26282657
* </div>
@@ -2631,17 +2660,16 @@ function primitives3D(p5, fn){
26312660
this._assert3d('instances');
26322661

26332662
if (typeof count !== 'number' || !isFinite(count) || count < 1) {
2634-
console.log(
2635-
'🌸 p5.js says: instances() requires a positive integer count.' +
2636-
' Clamping to 1.'
2663+
p5._friendlyError(
2664+
'instances() requires a positive integer count. Clamping to 1.',
2665+
'instances'
26372666
);
2638-
count = Math.max(1, Math.round(count) || 1);
2667+
count = 1;
26392668
} else {
26402669
count = Math.round(count);
26412670
}
26422671

26432672
const r = this._renderer;
2644-
const p = this;
26452673

26462674
// Each wrapped method: set _instanceCount, call the renderer method with
26472675
// the correct `this`, clear _instanceCount in finally so it never leaks.
@@ -2652,7 +2680,6 @@ function primitives3D(p5, fn){
26522680
} finally {
26532681
r._instanceCount = undefined;
26542682
}
2655-
return p;
26562683
};
26572684

26582685
return {

test/unit/visual/cases/webgl.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,25 @@ visualSuite('WebGL', function() {
11011101
p5.model(obj, numInstances);
11021102
screenshot();
11031103
});
1104+
1105+
visualTest('instances() API draws multiple spaced primitives', (p5, screenshot) => {
1106+
p5.createCanvas(50, 50, p5.WEBGL);
1107+
const count = 5;
1108+
const shader = p5.baseMaterialShader().modify(() => {
1109+
p5.getWorldInputs((inputs) => {
1110+
let spacing = p5.width / count;
1111+
inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing;
1112+
return inputs;
1113+
});
1114+
}, { p5 });
1115+
p5.background(220);
1116+
p5.lights();
1117+
p5.noStroke();
1118+
p5.fill('red');
1119+
p5.shader(shader);
1120+
p5.instances(count).sphere(7);
1121+
screenshot();
1122+
});
11041123
});
11051124

11061125
visualSuite('p5.strands', () => {

test/unit/visual/cases/webgpu.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,25 @@ visualSuite("WebGPU", function () {
326326
await screenshot();
327327
});
328328

329+
visualTest('instances() API draws multiple spaced primitives (WebGPU)', async function(p5, screenshot) {
330+
await p5.createCanvas(50, 50, p5.WEBGPU);
331+
const count = 5;
332+
const shader = p5.baseMaterialShader().modify(() => {
333+
p5.getWorldInputs((inputs) => {
334+
let spacing = p5.width / count;
335+
inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing;
336+
return inputs;
337+
});
338+
}, { p5 });
339+
p5.background(220);
340+
p5.lights();
341+
p5.noStroke();
342+
p5.fill('red');
343+
p5.shader(shader);
344+
p5.instances(count).sphere(7);
345+
await screenshot();
346+
});
347+
329348
visualTest('random() colors a basic shader (WebGPU)', async function(p5, screenshot) {
330349
await p5.createCanvas(50, 50, p5.WEBGPU);
331350
const shader = p5.baseColorShader().modify(() => {

utils/typescript.mjs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,19 +366,27 @@ function convertFunctionTypeForInterface(typeNode, options) {
366366
.map((param, i) => {
367367
let typeObj;
368368
let paramName;
369-
if (param.type === 'ParameterType') {
370-
typeObj = param.expression;
371-
paramName = param.name ?? `p${i}`;
372-
} else if (typeof param.type === 'object' && param.type !== null) {
373-
typeObj = param.type;
374-
paramName = param.name ?? `p${i}`;
369+
let isOptional = false;
370+
371+
let currentParam = param;
372+
if (currentParam && currentParam.type === 'OptionalType') {
373+
isOptional = true;
374+
currentParam = currentParam.expression;
375+
}
376+
377+
if (currentParam && currentParam.type === 'ParameterType') {
378+
typeObj = currentParam.expression;
379+
paramName = currentParam.name ?? `p${i}`;
380+
} else if (currentParam && typeof currentParam.type === 'object' && currentParam.type !== null) {
381+
typeObj = currentParam.type;
382+
paramName = currentParam.name ?? `p${i}`;
375383
} else {
376384
// param itself is a plain type node
377-
typeObj = param;
385+
typeObj = currentParam;
378386
paramName = `p${i}`;
379387
}
380388
const paramType = convertTypeToTypeScript(typeObj, options);
381-
return `${paramName}: ${paramType}`;
389+
return `${paramName}${isOptional ? '?' : ''}: ${paramType}`;
382390
})
383391
.join(', ');
384392

0 commit comments

Comments
 (0)