processing / processing/p5.js

[p5.js 2.0 Bug Report]: Tessellation of beginShape/endShape with multiple contours sometimes produces weird results

Open
#8,186 2 comments 0 reactions 1 assignee View on GitHub

@avinxshKD is already working on this.

Since Oct 24, 2025.

Area:WebGL p5.js 2.0+
Dominant language
JavaScript
Stars
24k
Forks
3.8k
Avg merge
3d 16h
Merged PRs (30d)
25

Description

Most appropriate sub-area of p5.js?
  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • Build process
  • Unit testing
  • Internationalization
  • Friendly errors
  • Other (specify if possible)
p5.js version

2.0.5, 1.11.10

Web browser and version

Firefox, Chrome

Operating system

MacOS

Steps to reproduce this
Steps:
  1. Use WebGL mode
  2. Draw a shape with two intersecting contours
  3. In one of the contours, make a vertex have one coordinate aaalmost equal to that coordinate of the previous, e.g. have x=1 and then x=1.00000001

The output of libtess is glitchy in this case:

Without a similar coordinateWith a similar coordinate
Image Image

I debugged this and our vertices all look right going into libtess, and our GLU_TESS_COMBINE callback is working properly, so this is actually a bug within libtess. But libtess is a JS port of SGI C code from the 90s, I doubt they're actually going to fix this bug, and it's pretty subtle, so this feels like something we'll have to work around ourselves if we want to work around it at all.

Workaround for users

Basically if they're drawing a bunch of points, they have to add a check like this before drawing the points:

for (let i = 1; i < points.length; i++) {
  const prev = points[i-1]
  const next = points[i]
  if (Math.abs(prev.x - next.x) < 1e-6) next.x = prev.x
  if (Math.abs(prev.y - next.y) < 1e-6) next.y = prev.y
}

A fix for this probably would involve us doing that loop ourselves before tessellating a shape.

Should we fix this at all?

It's unlikely that a user would manually create problematic coordinates like this, but it's a lot more likely to happen when drawing contours automatically sampled from a font with font.textToContours() (which is how I ran into this just now!) So if we do a fix, we can maybe consider it a fix for textToContours usage.

Snippet:
function setup() {
  createCanvas(400, 400, WEBGL);
}

function draw() {
  const contours = [
    [
      [-3.8642425537109375,-6.120738636363637,0],
      [3.2025188099254267,-6.120738636363637,0],
      [3.2025188099254267,-4.345170454545455,0],
      [-3.8642425537109375,-4.345170454545455,0],
      [-3.8642425537109375,-6.120738636363637,0],
    ],

    [
      [-1.8045834628018462,4.177556818181818,0],
      [-1.8045834628018462,-9.387784090909093,0],
      [0.29058699174360836,-9.387784090909093,0],
      ...(
        frameCount % 120 < 60
          ? [[0.2905869917436083,3.609374411367136,0]] // doesnt work
       // ? [[0.29058699174360836,3.609374411367136,0]] // works
          : []
      ),
      [0.31044303036623855,4.068235883781435,0],
      [0.38522861430307975,4.522728865422799,0],
      [0.548044378107245,4.941051136363637,0],
      [0.8364672032828204,5.2932224887960775,0],
      [1.2227602871981542,5.526988636363637,0],
      [1.6572258237923885,5.634502949876295,0],
      [2.101666537198154,5.669034090909091,0],
      [2.6695604948237173,5.633568761673102,0],
      [3.0249619917436084,5.5625,0],
      [3.4510983553799726,7.4446022727272725,0],
      [2.8568950819856695,7.613138889205699,0],
      [2.3751340936529037,7.676962586830456,0],
      [1.8892600236717598,7.693181792704519,0],
      [1.2922705720786674,7.649533731133848,0],
      [0.7080836288276859,7.519788939617751,0],
      [0.14854153719815422,7.311434659090909,0],
      [-0.38643934048179873,7.00959666478984,0],
      [-0.858113258144025,6.61653855366859,0],
      [-1.25415732643821,6.1484375,0],
      [-1.5108595282965422,5.697682732328092,0],
      [-1.6824918355513252,5.207533878495854,0],
      [-1.7762971052870198,4.695933154267308,0],
      [-1.8045834628018462,4.177556818181818,0],
    ]
  ]
  
  background('red')
  push()
  stroke(0)
  fill('#EEE')
  scale(15)
  beginShape()
  for (const contour of contours) {
    beginContour()
    for (const v of contour) {
      vertex(...v)
    }
    endContour()
  }
  endShape()
  
  stroke(0, 255, 0)
  strokeWeight(5)
  beginShape(POINTS)
  for (const contour of contours) {
    for (const v of contour) {
      vertex(...v)
    }
  }
  endShape()
  pop()
}

Live: https://editor.p5js.org/davepagurek/sketches/mvVHjXVjM

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.