diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 9fbcf5955c..03afb2105b 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -189,6 +189,15 @@ export class ShapeBuilder { this.shapeMode = constants.TRIANGLES; } + if ( + !this.renderer.geometryBuilder && + this.shapeMode === constants.TRIANGLE_FAN && + !this.renderer.supportsTriangleFan() + ) { + this._convertFanToTriangles(); + this.shapeMode = constants.TRIANGLES; + } + if ( this.renderer.states.textureMode === constants.IMAGE && this.renderer.states._tex !== null && @@ -205,6 +214,46 @@ export class ShapeBuilder { } } + _remapVertices(newIndices) { + this.geometry.vertices = newIndices.map(i => this.geometry.vertices[i]); + this.geometry.vertexNormals = newIndices.map(i => this.geometry.vertexNormals[i]); + + const remapFlat = (arr, stride) => { + const result = []; + for (const i of newIndices) { + for (let j = 0; j < stride; j++) { + result.push(arr[i * stride + j]); + } + } + return result; + }; + + this.geometry.uvs = remapFlat(this.geometry.uvs, 2); + this.geometry.vertexColors = remapFlat(this.geometry.vertexColors, 4); + this.geometry.vertexStrokeColors = remapFlat(this.geometry.vertexStrokeColors, 4); + + for (const propName in this.geometry.userVertexProperties) { + const prop = this.geometry.userVertexProperties[propName]; + const size = prop.getDataSize(); + const oldData = prop.getSrcArray(); + prop.resetSrcArray(); + for (const i of newIndices) { + prop.setCurrentData(oldData.slice(i * size, i * size + size)); + prop.pushCurrentData(); + } + } + } + + _convertFanToTriangles() { + const n = this.geometry.vertices.length; + if (n < 3) return; + const newIndices = []; + for (let i = 2; i < n; i++) { + newIndices.push(0, i - 1, i); + } + this._remapVertices(newIndices); + } + _resetUserVertexProperties() { const properties = this.geometry.userVertexProperties; for (const propName in properties){ diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 2fa7b51d6c..502a5c6af8 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -297,7 +297,14 @@ class RendererGL extends Renderer3D { } } } else { - const glMode = mode === constants.TRIANGLES ? gl.TRIANGLES : gl.TRIANGLE_STRIP; + let glMode; + if (mode === constants.TRIANGLES) { + glMode = gl.TRIANGLES; + } else if (mode === constants.TRIANGLE_FAN) { + glMode = gl.TRIANGLE_FAN; + } else { + glMode = gl.TRIANGLE_STRIP; + } if (count === 1) { gl.drawArrays(glMode, 0, geometry.vertices.length); } else { @@ -595,6 +602,10 @@ class RendererGL extends Renderer3D { return 10; } + supportsTriangleFan() { + return true; + } + viewport(w, h) { this._viewport = [0, 0, w, h]; this.GL.viewport(0, 0, w, h); diff --git a/src/webgpu/p5.RendererWebGPU.js b/src/webgpu/p5.RendererWebGPU.js index 3b909230b7..47f20b700d 100644 --- a/src/webgpu/p5.RendererWebGPU.js +++ b/src/webgpu/p5.RendererWebGPU.js @@ -1325,6 +1325,10 @@ function rendererWebGPU(p5, fn) { ); } + supportsTriangleFan() { + return false; + } + viewport() {} zClipRange() { diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 972d9bc5f0..618078bc81 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -1627,6 +1627,72 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre p5.rect(-20, -20, 40, 40, 20); screenshot(); }); + + visualTest('TRIANGLE_FAN with per-vertex fills', function(p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(255); + p5.beginShape(p5.TRIANGLE_FAN); + p5.fill('red'); + p5.vertex(0, 0); + const n = 10; + const r = 20; + p5.fill('blue'); + for (let i = 0; i <= n; i++) { + const angle = i/n * p5.TWO_PI; + p5.vertex(r*p5.cos(angle), r*p5.sin(angle)); + } + p5.endShape(); + screenshot(); + }); + + visualTest('TRIANGLE_FAN in p5.Geometry with per-vertex fills', function(p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(255); + const geom = p5.buildGeometry(() => { + p5.beginShape(p5.TRIANGLE_FAN); + p5.fill('red'); + p5.vertex(0, 0); + const n = 10; + const r = 20; + p5.fill('blue'); + for (let i = 0; i <= n; i++) { + const angle = i/n * p5.TWO_PI; + p5.vertex(r*p5.cos(angle), r*p5.sin(angle)); + } + p5.endShape(); + }); + p5.model(geom); + screenshot(); + }); + + visualTest('TRIANGLE_STRIP with per-vertex fills', function(p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(255); + p5.beginShape(p5.TRIANGLE_STRIP); + const n = 6; + for (let i = 0; i < n; i++) { + p5.fill(i % 2 === 0 ? 'red' : 'blue'); + p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); + } + p5.endShape(); + screenshot(); + }); + + visualTest('TRIANGLE_STRIP in p5.Geometry with per-vertex fills', function(p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(255); + const geom = p5.buildGeometry(() => { + p5.beginShape(p5.TRIANGLE_STRIP); + const n = 6; + for (let i = 0; i < n; i++) { + p5.fill(i % 2 === 0 ? 'red' : 'blue'); + p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); + } + p5.endShape(); + }); + p5.model(geom); + screenshot(); + }); }); visualSuite('3D Primitives', function() { diff --git a/test/unit/visual/cases/webgpu.js b/test/unit/visual/cases/webgpu.js index 4fc098850b..c789520258 100644 --- a/test/unit/visual/cases/webgpu.js +++ b/test/unit/visual/cases/webgpu.js @@ -382,7 +382,7 @@ visualSuite("WebGPU", function () { p5.shader(shader); p5.plane(20, 20); await screenshot(); - }, { focus: true }); + }); }); visualTest('randomGaussian() colors a basic shader (WebGPU)', async function(p5, screenshot) { @@ -1664,6 +1664,74 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', ); }); + visualSuite('2D Shapes', function() { + visualTest('TRIANGLE_FAN with per-vertex fills', async function(p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background(255); + p5.beginShape(p5.TRIANGLE_FAN); + p5.fill('red'); + p5.vertex(0, 0); + const n = 10; + const r = 20; + p5.fill('blue'); + for (let i = 0; i <= n; i++) { + const angle = i/n * p5.TWO_PI; + p5.vertex(r*p5.cos(angle), r*p5.sin(angle)); + } + p5.endShape(); + await screenshot(); + }); + + visualTest('TRIANGLE_FAN in p5.Geometry with per-vertex fills', async function(p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background(255); + const geom = p5.buildGeometry(() => { + p5.beginShape(p5.TRIANGLE_FAN); + p5.fill('red'); + p5.vertex(0, 0); + const n = 10; + const r = 20; + p5.fill('blue'); + for (let i = 0; i <= n; i++) { + const angle = i/n * p5.TWO_PI; + p5.vertex(r*p5.cos(angle), r*p5.sin(angle)); + } + p5.endShape(); + }); + p5.model(geom); + await screenshot(); + }); + + visualTest('TRIANGLE_STRIP with per-vertex fills', async function(p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background(255); + p5.beginShape(p5.TRIANGLE_STRIP); + const n = 6; + for (let i = 0; i < n; i++) { + p5.fill(i % 2 === 0 ? 'red' : 'blue'); + p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); + } + p5.endShape(); + await screenshot(); + }); + + visualTest('TRIANGLE_STRIP in p5.Geometry with per-vertex fills', async function(p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background(255); + const geom = p5.buildGeometry(() => { + p5.beginShape(p5.TRIANGLE_STRIP); + const n = 6; + for (let i = 0; i < n; i++) { + p5.fill(i % 2 === 0 ? 'red' : 'blue'); + p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); + } + p5.endShape(); + }); + p5.model(geom); + await screenshot(); + }); + }); + visualSuite('Feedback', function() { visualTest( 'Drawing accumulates across frames when background is set in setup', diff --git a/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/000.png b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/000.png new file mode 100644 index 0000000000..f0534658c3 Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/metadata.json b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN with per-vertex fills/000.png b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN with per-vertex fills/000.png new file mode 100644 index 0000000000..f0534658c3 Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN with per-vertex fills/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN with per-vertex fills/metadata.json b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN with per-vertex fills/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_FAN with per-vertex fills/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/000.png b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/000.png new file mode 100644 index 0000000000..029920bec7 Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/metadata.json b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP with per-vertex fills/000.png b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP with per-vertex fills/000.png new file mode 100644 index 0000000000..029920bec7 Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP with per-vertex fills/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP with per-vertex fills/metadata.json b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP with per-vertex fills/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/2D Shapes/TRIANGLE_STRIP with per-vertex fills/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/000.png b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/000.png new file mode 100644 index 0000000000..78d1441c55 Binary files /dev/null and b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/000.png differ diff --git a/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/metadata.json b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN in p5.Geometry with per-vertex fills/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN with per-vertex fills/000.png b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN with per-vertex fills/000.png new file mode 100644 index 0000000000..71c91bb91d Binary files /dev/null and b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN with per-vertex fills/000.png differ diff --git a/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN with per-vertex fills/metadata.json b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN with per-vertex fills/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_FAN with per-vertex fills/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/000.png b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/000.png new file mode 100644 index 0000000000..8257a3adda Binary files /dev/null and b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/000.png differ diff --git a/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/metadata.json b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP in p5.Geometry with per-vertex fills/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP with per-vertex fills/000.png b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP with per-vertex fills/000.png new file mode 100644 index 0000000000..8257a3adda Binary files /dev/null and b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP with per-vertex fills/000.png differ diff --git a/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP with per-vertex fills/metadata.json b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP with per-vertex fills/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGPU/2D Shapes/TRIANGLE_STRIP with per-vertex fills/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file