diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js index 31ce48f630..8e7e4bfb89 100644 --- a/src/webgl/p5.RendererGL.Immediate.js +++ b/src/webgl/p5.RendererGL.Immediate.js @@ -436,6 +436,37 @@ p5.RendererGL.prototype._tesselateShape = function() { this.immediateMode.geometry.vertexNormals[i].z ); } + + // Normalize nearly identical consecutive vertices to avoid numerical issues in libtess. + // This workaround addresses tessellation artifacts when consecutive vertices have + // coordinates that are almost (but not exactly) equal, which can occur when drawing + // contours automatically sampled from fonts with font.textToContours(). + const epsilon = 1e-6; + for (const contour of contours) { + for ( + let i = p5.RendererGL.prototype.tessyVertexSize; + i < contour.length; + i += p5.RendererGL.prototype.tessyVertexSize + ) { + const prevX = contour[i - p5.RendererGL.prototype.tessyVertexSize]; + const prevY = contour[i - p5.RendererGL.prototype.tessyVertexSize + 1]; + const prevZ = contour[i - p5.RendererGL.prototype.tessyVertexSize + 2]; + const currX = contour[i]; + const currY = contour[i + 1]; + const currZ = contour[i + 2]; + + if (Math.abs(prevX - currX) < epsilon) { + contour[i] = prevX; + } + if (Math.abs(prevY - currY) < epsilon) { + contour[i + 1] = prevY; + } + if (Math.abs(prevZ - currZ) < epsilon) { + contour[i + 2] = prevZ; + } + } + } + const polyTriangles = this._triangulate(contours); const originalVertices = this.immediateMode.geometry.vertices; this.immediateMode.geometry.vertices = []; diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 2f36feb806..0bf2fd7292 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -141,4 +141,35 @@ visualSuite('WebGL', function() { screenshot(); }); }); + + visualSuite('Tessellation', function() { + visualTest('Handles nearly identical consecutive vertices', function(p5, screenshot) { + p5.createCanvas(100, 100, p5.WEBGL); + p5.pixelDensity(1); + p5.background(255); + p5.fill(0); + p5.noStroke(); + + // Contours with nearly identical consecutive vertices (as can occur with textToContours) + // Outer contour + p5.beginShape(); + p5.vertex(-30, -30, 0); + p5.vertex(30, -30, 0); + p5.vertex(30, 30, 0); + p5.vertex(-30, 30, 0); + + // Inner contour (hole) with nearly identical vertices + p5.beginContour(); + p5.vertex(-10, -10, 0); + p5.vertex(-10, 10, 0); + // This vertex has x coordinate almost equal to previous (10.00000001 vs 10) + p5.vertex(10.00000001, 10, 0); + p5.vertex(10, -10, 0); + p5.endContour(); + + p5.endShape(p5.CLOSE); + + screenshot(); + }); + }); });