Skip to content

Add support for beginContour() and endContour() in Webgl mode #6297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/core/p5.Renderer2D.js
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,7 @@ class Renderer2D extends p5.Renderer{
v = vertices[i];
if (v.isVert) {
if (v.moveTo) {
if (closeShape) this.drawingContext.closePath();
this.drawingContext.moveTo(v[0], v[1]);
} else {
this.drawingContext.lineTo(v[0], v[1]);
Expand Down
12 changes: 10 additions & 2 deletions src/core/shape/vertex.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ let isFirstContour = true;
* white rect and smaller grey rect with red outlines in center of canvas.
*/
p5.prototype.beginContour = function() {
contourVertices = [];
isContour = true;
if (this._renderer.isP3D) {
this._renderer.beginContour();
} else {
contourVertices = [];
isContour = true;
}
return this;
};

Expand Down Expand Up @@ -563,6 +567,10 @@ p5.prototype.curveVertex = function(...args) {
* white rect and smaller grey rect with red outlines in center of canvas.
*/
p5.prototype.endContour = function() {
if (this._renderer.isP3D) {
return this;
}

const vert = contourVertices[0].slice(); // copy all data
vert.isVert = contourVertices[0].isVert;
vert.moveTo = false;
Expand Down
95 changes: 65 additions & 30 deletions src/webgl/p5.Geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,8 @@ p5.Geometry = class {
this.lineTangentsOut.length = 0;
this.lineSides.length = 0;

const closed =
this.edges.length > 1 &&
this.edges[0][0] === this.edges[this.edges.length - 1][1];
let addedStartingCap = false;
const potentialCaps = new Map();
const connected = new Set();
let lastValidDir;
for (let i = 0; i < this.edges.length; i++) {
const prevEdge = this.edges[i - 1];
Expand Down Expand Up @@ -298,53 +296,90 @@ p5.Geometry = class {
}

if (i > 0 && prevEdge[1] === currEdge[0]) {
// Add a join if this segment shares a vertex with the previous. Skip
// actually adding join vertices if either the previous segment or this
// one has a length of 0.
//
// Don't add a join if the tangents point in the same direction, which
// would mean the edges line up exactly, and there is no need for a join.
if (lastValidDir && dirOK && dir.dot(lastValidDir) < 1 - 1e-8) {
this._addJoin(begin, lastValidDir, dir, fromColor);
}
if (dirOK && !addedStartingCap && !closed) {
this._addCap(begin, dir.copy().mult(-1), fromColor);
addedStartingCap = true;
if (!connected.has(currEdge[0])) {
connected.add(currEdge[0]);
potentialCaps.delete(currEdge[0]);
// Add a join if this segment shares a vertex with the previous. Skip
// actually adding join vertices if either the previous segment or this
// one has a length of 0.
//
// Don't add a join if the tangents point in the same direction, which
// would mean the edges line up exactly, and there is no need for a join.
if (lastValidDir && dirOK && dir.dot(lastValidDir) < 1 - 1e-8) {
this._addJoin(begin, lastValidDir, dir, fromColor);
}
}
} else {
addedStartingCap = false;
// Start a new line
if (dirOK && (!closed || i > 0)) {
this._addCap(begin, dir.copy().mult(-1), fromColor);
addedStartingCap = true;
if (dirOK && !connected.has(currEdge[0])) {
const existingCap = potentialCaps.get(currEdge[0]);
if (existingCap) {
this._addJoin(
begin,
existingCap.dir,
dir,
fromColor
);
potentialCaps.delete(currEdge[0]);
connected.add(currEdge[0]);
} else {
potentialCaps.set(currEdge[0], {
point: begin,
dir: dir.copy().mult(-1),
color: fromColor
});
}
}
if (lastValidDir && (!closed || i < this.edges.length - 1)) {
// Close off the last segment with a cap
this._addCap(this.vertices[prevEdge[1]], lastValidDir, fromColor);
if (lastValidDir && !connected.has(prevEdge[1])) {
const existingCap = potentialCaps.get(prevEdge[1]);
if (existingCap) {
this._addJoin(
this.vertices[prevEdge[1]],
lastValidDir,
existingCap.dir.copy().mult(-1),
fromColor
);
potentialCaps.delete(prevEdge[1]);
connected.add(prevEdge[1]);
} else {
// Close off the last segment with a cap
potentialCaps.set(prevEdge[1], {
point: this.vertices[prevEdge[1]],
dir: lastValidDir,
color: fromColor
});
}
lastValidDir = undefined;
}
}

if (i === this.edges.length - 1) {
if (closed) {
if (i === this.edges.length - 1 && !connected.has(currEdge[1])) {
const existingCap = potentialCaps.get(currEdge[1]);
if (existingCap) {
this._addJoin(
end,
dir,
this.vertices[this.edges[0][1]]
.copy()
.sub(end)
.normalize(),
existingCap.dir.copy().mult(-1),
toColor
);
potentialCaps.delete(currEdge[1]);
connected.add(currEdge[1]);
} else {
this._addCap(end, dir, toColor);
potentialCaps.set(currEdge[1], {
point: end,
dir,
color: toColor
});
}
}

if (dirOK) {
lastValidDir = dir;
}
}
for (const { point, dir, color } of potentialCaps.values()) {
this._addCap(point, dir, color);
}
return this;
}

Expand Down
63 changes: 52 additions & 11 deletions src/webgl/p5.RendererGL.Immediate.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ p5.RendererGL.prototype.beginShape = function(mode) {
this.immediateMode.shapeMode =
mode !== undefined ? mode : constants.TESS;
this.immediateMode.geometry.reset();
this.immediateMode.contourIndices = [];
return this;
};

Expand All @@ -45,6 +46,15 @@ const immediateBufferStrides = {
uvs: 2
};

p5.RendererGL.prototype.beginContour = function() {
if (this.immediateMode.shapeMode !== constants.TESS) {
throw new Error('WebGL mode can only use contours with beginShape(TESS).');
}
this.immediateMode.contourIndices.push(
this.immediateMode.geometry.vertices.length
);
};

/**
* adds a vertex to be drawn in a custom Shape.
* @private
Expand Down Expand Up @@ -238,10 +248,19 @@ p5.RendererGL.prototype._processVertices = function(mode) {
}
// For hollow shapes, user must set mode to TESS
const convexShape = this.immediateMode.shapeMode === constants.TESS;
// If the shape has a contour, we have to re-triangulate to cut out the
// contour region
const hasContour = this.immediateMode.contourIndices.length > 0;
// We tesselate when drawing curves or convex shapes
const shouldTess =
this._doFill &&
(this.isBezier || this.isQuadratic || this.isCurve || convexShape) &&
(
this.isBezier ||
this.isQuadratic ||
this.isCurve ||
convexShape ||
hasContour
) &&
this.immediateMode.shapeMode !== constants.LINES;

if (shouldTess) {
Expand All @@ -262,6 +281,8 @@ p5.RendererGL.prototype._calculateEdges = function(
) {
const res = [];
let i = 0;
const contourIndices = this.immediateMode.contourIndices.slice();
let contourStart = 0;
switch (shapeMode) {
case constants.TRIANGLE_STRIP:
for (i = 0; i < verts.length - 2; i++) {
Expand Down Expand Up @@ -313,12 +334,23 @@ p5.RendererGL.prototype._calculateEdges = function(
res.push([i, i + 1]);
break;
default:
for (i = 0; i < verts.length - 1; i++) {
res.push([i, i + 1]);
// TODO: handle contours in other modes too
for (i = 0; i < verts.length; i++) {
// Handle breaks between contours
if (i + 1 < verts.length && i + 1 !== contourIndices[0]) {
res.push([i, i + 1]);
} else {
if (shouldClose || contourStart) {
res.push([i, contourStart]);
}
if (contourIndices.length > 0) {
contourStart = contourIndices.shift();
}
}
}
break;
}
if (shouldClose) {
if (shapeMode !== constants.TESS && shouldClose) {
res.push([verts.length - 1, 0]);
}
return res;
Expand All @@ -329,12 +361,21 @@ p5.RendererGL.prototype._calculateEdges = function(
* @private
*/
p5.RendererGL.prototype._tesselateShape = function() {
// TODO: handle non-TESS shape modes that have contours
this.immediateMode.shapeMode = constants.TRIANGLES;
const contours = [
this._flatten(this.immediateMode.geometry.vertices.map((vert, i) => [
vert.x,
vert.y,
vert.z,
const contours = [[]];
for (let i = 0; i < this.immediateMode.geometry.vertices.length; i++) {
if (
this.immediateMode.contourIndices.length > 0 &&
this.immediateMode.contourIndices[0] === i
) {
this.immediateMode.contourIndices.shift();
contours.push([]);
}
contours[contours.length-1].push(
this.immediateMode.geometry.vertices[i].x,
this.immediateMode.geometry.vertices[i].y,
this.immediateMode.geometry.vertices[i].z,
this.immediateMode.geometry.uvs[i * 2],
this.immediateMode.geometry.uvs[i * 2 + 1],
this.immediateMode.geometry.vertexColors[i * 4],
Expand All @@ -344,8 +385,8 @@ p5.RendererGL.prototype._tesselateShape = function() {
this.immediateMode.geometry.vertexNormals[i].x,
this.immediateMode.geometry.vertexNormals[i].y,
this.immediateMode.geometry.vertexNormals[i].z
]))
];
);
}
const polyTriangles = this._triangulate(contours);
this.immediateMode.geometry.vertices = [];
this.immediateMode.geometry.vertexNormals = [];
Expand Down
5 changes: 5 additions & 0 deletions src/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this.immediateMode = {
geometry: new p5.Geometry(),
shapeMode: constants.TRIANGLE_FAN,
contourIndices: [],
_bezierVertex: [],
_quadraticVertex: [],
_curveVertex: [],
Expand Down Expand Up @@ -1763,6 +1764,10 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR, errorcallback);
tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_COMBINE, combinecallback);
tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_EDGE_FLAG, edgeCallback);
tessy.gluTessProperty(
libtess.gluEnum.GLU_TESS_WINDING_RULE,
libtess.windingRule.GLU_TESS_WINDING_NONZERO
);

return tessy;
}
Expand Down
34 changes: 34 additions & 0 deletions test/unit/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,40 @@ suite('p5.RendererGL', function() {
});
});

test('contours match 2D', function() {
const getColors = function(mode) {
myp5.createCanvas(50, 50, mode);
myp5.pixelDensity(1);
myp5.background(200);
myp5.strokeCap(myp5.SQUARE);
myp5.strokeJoin(myp5.MITER);
if (mode === myp5.WEBGL) {
myp5.translate(-myp5.width/2, -myp5.height/2);
}
myp5.stroke('black');
myp5.strokeWeight(2);
myp5.translate(25, 25);
myp5.beginShape();
// Exterior part of shape, clockwise winding
myp5.vertex(-20, -20);
myp5.vertex(20, -20);
myp5.vertex(20, 20);
myp5.vertex(-20, 20);
// Interior part of shape, counter-clockwise winding
myp5.beginContour();
myp5.vertex(-10, -10);
myp5.vertex(-10, 10);
myp5.vertex(10, 10);
myp5.vertex(10, -10);
myp5.endContour();
myp5.endShape(myp5.CLOSE);
myp5.loadPixels();
return [...myp5.pixels];
};

assert.deepEqual(getColors(myp5.P2D), getColors(myp5.WEBGL));
});

suite('text shader', function() {
test('rendering looks the same in WebGL1 and 2', function(done) {
myp5.loadFont('manual-test-examples/p5.Font/Inconsolata-Bold.ttf', function(font) {
Expand Down