From 94e1969a3219cb87caa24eda1e0144e03d9662b0 Mon Sep 17 00:00:00 2001 From: ksen0 Date: Mon, 2 Sep 2024 07:42:47 +0200 Subject: [PATCH 1/3] Refactored p5 arc to use canvas ellipse --- src/core/p5.Renderer2D.js | 107 +++++++++----------------------------- 1 file changed, 24 insertions(+), 83 deletions(-) diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 1e7506558b..e13ea2bb0d 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -532,42 +532,6 @@ class Renderer2D extends p5.Renderer { // SHAPE | 2D Primitives ////////////////////////////////////////////// - /** - * Generate a cubic Bezier representing an arc on the unit circle of total - * angle `size` radians, beginning `start` radians above the x-axis. Up to - * four of these curves are combined to make a full arc. - * - * See ecridge.com/bezier.pdf for an explanation of the method. - */ - _acuteArcToBezier( - start, - size - ) { - // Evaluate constants. - const alpha = size / 2.0, - cos_alpha = Math.cos(alpha), - sin_alpha = Math.sin(alpha), - cot_alpha = 1.0 / Math.tan(alpha), - // This is how far the arc needs to be rotated. - phi = start + alpha, - cos_phi = Math.cos(phi), - sin_phi = Math.sin(phi), - lambda = (4.0 - cos_alpha) / 3.0, - mu = sin_alpha + (cos_alpha - lambda) * cot_alpha; - - // Return rotated waypoints. - return { - ax: Math.cos(start).toFixed(7), - ay: Math.sin(start).toFixed(7), - bx: (lambda * cos_phi + mu * sin_phi).toFixed(7), - by: (lambda * sin_phi - mu * cos_phi).toFixed(7), - cx: (lambda * cos_phi - mu * sin_phi).toFixed(7), - cy: (lambda * sin_phi + mu * cos_phi).toFixed(7), - dx: Math.cos(start + size).toFixed(7), - dy: Math.sin(start + size).toFixed(7) - }; - } - /* * This function requires that: * @@ -577,64 +541,41 @@ class Renderer2D extends p5.Renderer { */ arc(x, y, w, h, start, stop, mode) { const ctx = this.drawingContext; - const rx = w / 2.0; - const ry = h / 2.0; - const epsilon = 0.00001; // Smallest visible angle on displays up to 4K. - let arcToDraw = 0; - const curves = []; - - x += rx; - y += ry; - - // Create curves - while (stop - start >= epsilon) { - arcToDraw = Math.min(stop - start, constants.HALF_PI); - curves.push(this._acuteArcToBezier(start, arcToDraw)); - start += arcToDraw; - } - // Fill curves + const centerX = x + w / 2, + centerY = y + h / 2, + radiusX = w / 2, + radiusY = h / 2; + + // Determines whether to add a line to the center, which should be done + // when the mode is PIE or default, as well as when the start and end + // angles do not form a full circle. + const createPieSlice = ! ( + mode === constants.CHORD || + mode === constants.OPEN || + (stop - start) % constants.TWO_PI === 0 + ); + + // Fill if (this._doFill) { if (!this._clipping) ctx.beginPath(); - curves.forEach((curve, index) => { - if (index === 0) { - ctx.moveTo(x + curve.ax * rx, y + curve.ay * ry); - } - /* eslint-disable indent */ - ctx.bezierCurveTo(x + curve.bx * rx, y + curve.by * ry, - x + curve.cx * rx, y + curve.cy * ry, - x + curve.dx * rx, y + curve.dy * ry); - /* eslint-enable indent */ - }); - if (mode === constants.PIE || mode == null) { - ctx.lineTo(x, y); - } + ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, start, stop); + if (createPieSlice) ctx.lineTo(centerX, centerY); ctx.closePath(); if (!this._clipping) ctx.fill(); } - // Stroke curves + // Stroke if (this._doStroke) { if (!this._clipping) ctx.beginPath(); - curves.forEach((curve, index) => { - if (index === 0) { - ctx.moveTo(x + curve.ax * rx, y + curve.ay * ry); - } - /* eslint-disable indent */ - ctx.bezierCurveTo(x + curve.bx * rx, y + curve.by * ry, - x + curve.cx * rx, y + curve.cy * ry, - x + curve.dx * rx, y + curve.dy * ry); - /* eslint-enable indent */ - }); - if (mode === constants.PIE) { - ctx.lineTo(x, y); - ctx.closePath(); - } else if (mode === constants.CHORD) { - ctx.closePath(); - } + ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, start, stop); + if (createPieSlice) ctx.lineTo(centerX, centerY); + if (mode !== constants.OPEN) ctx.closePath(); if (!this._clipping) ctx.stroke(); } + return this; + } ellipse(args) { @@ -659,7 +600,7 @@ class Renderer2D extends p5.Renderer { radiusX = w / 2, radiusY = h / 2; if (!this._clipping) ctx.beginPath(); - + ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, 2 * Math.PI); if (!this._clipping && doFill) { From a975515e9ea0a43d89ed522f281d8c9433ede96c Mon Sep 17 00:00:00 2001 From: ksen0 Date: Mon, 2 Sep 2024 08:12:20 +0200 Subject: [PATCH 2/3] Refactor rect to use canvas roundRect --- src/core/p5.Renderer2D.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index e13ea2bb0d..865559ded1 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -548,7 +548,7 @@ class Renderer2D extends p5.Renderer { radiusY = h / 2; // Determines whether to add a line to the center, which should be done - // when the mode is PIE or default, as well as when the start and end + // when the mode is PIE or default; as well as when the start and end // angles do not form a full circle. const createPieSlice = ! ( mode === constants.CHORD || @@ -600,7 +600,7 @@ class Renderer2D extends p5.Renderer { radiusX = w / 2, radiusY = h / 2; if (!this._clipping) ctx.beginPath(); - + ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, 2 * Math.PI); if (!this._clipping && doFill) { @@ -745,14 +745,7 @@ class Renderer2D extends p5.Renderer { bl = hh; } - // Draw shape - if (!this._clipping) ctx.beginPath(); - ctx.moveTo(x + tl, y); - ctx.arcTo(x + w, y, x + w, y + h, tr); - ctx.arcTo(x + w, y + h, x, y + h, br); - ctx.arcTo(x, y + h, x, y, bl); - ctx.arcTo(x, y, x + w, y, tl); - ctx.closePath(); + ctx.roundRect(x, y, w, h, [tl, tr, br, bl]); } if (!this._clipping && this._doFill) { ctx.fill(); From 967b676fa69a1b5f4aee1f0c1c106cb1bf55c6e1 Mon Sep 17 00:00:00 2001 From: ksen0 Date: Tue, 3 Sep 2024 08:14:35 +0200 Subject: [PATCH 3/3] Clarify arc stroke behavior in different modes --- src/core/p5.Renderer2D.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 865559ded1..436fea7cbf 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -569,8 +569,18 @@ class Renderer2D extends p5.Renderer { if (this._doStroke) { if (!this._clipping) ctx.beginPath(); ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, start, stop); - if (createPieSlice) ctx.lineTo(centerX, centerY); - if (mode !== constants.OPEN) ctx.closePath(); + + if (mode === constants.PIE && createPieSlice) { + // In PIE mode, stroke is added to the center and back to path, + // unless the pie forms a complete ellipse (see: createPieSlice) + ctx.lineTo(centerX, centerY); + } + + if (mode === constants.PIE || mode === constants.CHORD) { + // Stroke connects back to path begin for both PIE and CHORD + ctx.closePath(); + } + if (!this._clipping) ctx.stroke(); }