From 1537aab3085fbf833a1720cb6353d76f0737f5ca Mon Sep 17 00:00:00 2001 From: ChrisCPI <92183829+ChrisCPI@users.noreply.github.com> Date: Wed, 28 May 2025 00:00:04 -0400 Subject: [PATCH 1/7] A rectangle can now have different radiuses for each rounded corner --- src/gameobjects/shape/rectangle/Rectangle.js | 139 +++++++++++++++---- 1 file changed, 113 insertions(+), 26 deletions(-) diff --git a/src/gameobjects/shape/rectangle/Rectangle.js b/src/gameobjects/shape/rectangle/Rectangle.js index f82ef34683..8bb2791ec9 100644 --- a/src/gameobjects/shape/rectangle/Rectangle.js +++ b/src/gameobjects/shape/rectangle/Rectangle.js @@ -55,30 +55,52 @@ var Rectangle = new Class({ Shape.call(this, scene, 'Rectangle', new GeomRectangle(0, 0, width, height)); /** - * The radius of the rectangle if this is set to use rounded corners. + * The radius of the top-left corner of the rectangle. * * Do not modify this property. Instead, call the method `setRounded` to set the * radius of the rounded corners. * - * @name Phaser.GameObjects.Shape#radius + * @name Phaser.GameObjects.Rectangle#tintTopLeft * @type {number} * @readonly - * @since 3.90.0 */ - this.radius = 20; + this.radiusTopLeft = 0; /** - * Does this Rectangle have rounded corners? + * The radius of the top-right corner of the rectangle. * * Do not modify this property. Instead, call the method `setRounded` to set the - * radius state of this rectangle. + * radius of the rounded corners. + * + * @name Phaser.GameObjects.Rectangle#tintTopLeft + * @type {number} + * @readonly + */ + this.radiusTopRight = 0; + + /** + * The radius of the bottom-left corner of the rectangle. + * + * Do not modify this property. Instead, call the method `setRounded` to set the + * radius of the rounded corners. + * + * @name Phaser.GameObjects.Rectangle#tintTopLeft + * @type {number} + * @readonly + */ + this.radiusBottomLeft = 0; + + /** + * The radius of the bottom-right corner of the rectangle. + * + * Do not modify this property. Instead, call the method `setRounded` to set the + * radius of the rounded corners. * - * @name Phaser.GameObjects.Shape#isRounded - * @type {boolean} + * @name Phaser.GameObjects.Rectangle#tintTopLeft + * @type {number} * @readonly - * @since 3.90.0 */ - this.isRounded = false; + this.radiusBottomRight = 0; this.setPosition(x, y); this.setSize(width, height); @@ -92,26 +114,85 @@ var Rectangle = new Class({ this.updateData(); }, + /** + * The radius of all the corners of the rectangle if this is set to use rounded corners. + * Return `radiusTopLeft` when read this radius property. + * + * @name Phaser.GameObjects.Shape#radius + * @type {number} + * @since 3.90.0 + */ + radius: { + + get: function () + { + return this.radiusTopLeft; + }, + + set: function (value) + { + this.setRounded(value, value, value, value); + } + + }, + + /** + * Does this Rectangle have rounded corners? + * + * It checks to see if the 4 radius properties are set to 0. + * This indicates that a Rectangle isn't rounded. + * + * @name Phaser.GameObjects.Shape#isRounded + * @type {boolean} + * @readonly + * @since 3.90.0 + */ + isRounded: { + + get: function () + { + return ( + this.radiusTopLeft !== 0 || + this.radiusTopRight !== 0 || + this.radiusBottomLeft !== 0 || + this.radiusBottomRight !== 0 + ); + } + + }, + /** * Sets this rectangle to have rounded corners by specifying the radius of the corner. * * The radius of the rounded corners is limited by the smallest dimension of the rectangle. * - * To disable rounded corners, set the `radius` parameter to 0. + * To disable rounded corners, set the `topLeft` parameter to 0. * * @method Phaser.GameObjects.Rectangle#setRounded * @since 3.90.0 * - * @param {number} [radius=16] - The radius of all four rounded corners. + * @param {number} [topLeft=16] - The radius of the top-left corner. If no other values are given this value is applied evenly, setting all corners to this radius. + * @param {number} [topRight] - The radius of the top-right corner. + * @param {number} [bottomLeft] - The radius of the bottom-left corner. + * @param {number} [bottomRight] - The radius of the bottom-right corner. * * @return {this} This Game Object instance. */ - setRounded: function (radius) + setRounded: function (topLeft, topRight, bottomLeft, bottomRight) { - if (radius === undefined) { radius = 16; } + if (topLeft === undefined) { topLeft = 16; } + + if (topRight === undefined) + { + topRight = topLeft; + bottomLeft = topLeft; + bottomRight = topLeft; + } - this.radius = radius; - this.isRounded = radius > 0; + this.radiusTopLeft = topLeft; + this.radiusTopRight = topRight; + this.radiusBottomLeft = bottomLeft; + this.radiusBottomRight = bottomRight; return this.updateRoundedData(); }, @@ -210,36 +291,42 @@ var Rectangle = new Class({ // Limit max radius to half the smallest dimension var maxRadius = Math.min(halfWidth, halfHeight); - var radius = Math.min(this.radius, maxRadius); + var tl = Math.min(this.radiusTopLeft, maxRadius); + var tr = Math.min(this.radiusTopRight, maxRadius); + var bl = Math.min(this.radiusBottomLeft, maxRadius); + var br = Math.min(this.radiusBottomRight, maxRadius); var x = halfWidth; var y = halfHeight; // The number of segments is based on radius (more segments = larger radius) - var segments = Math.max(1, Math.floor(radius / 5)); + var tlSegments = Math.max(1, Math.floor(tl)); + var trSegments = Math.max(1, Math.floor(tr)); + var blSegments = Math.max(1, Math.floor(bl)); + var brSegments = Math.max(1, Math.floor(br)); // Create points going clockwise from top-left // Top-left corner - this.arcTo(path, x - halfWidth + radius, y - halfHeight + radius, radius, Math.PI, Math.PI * 1.5, segments); + this.arcTo(path, x - halfWidth + tl, y - halfHeight + tl, tl, Math.PI, Math.PI * 1.5, tlSegments); // Top edge and top-right corner - path.push(x + halfWidth - radius, y - halfHeight); + path.push(x + halfWidth - tr, y - halfHeight); - this.arcTo(path, x + halfWidth - radius, y - halfHeight + radius, radius, Math.PI * 1.5, Math.PI * 2, segments); + this.arcTo(path, x + halfWidth - tr, y - halfHeight + tr, tr, Math.PI * 1.5, Math.PI * 2, trSegments); // Right edge and bottom-right corner - path.push(x + halfWidth, y + halfHeight - radius); + path.push(x + halfWidth, y + halfHeight - br); - this.arcTo(path, x + halfWidth - radius, y + halfHeight - radius, radius, 0, Math.PI * 0.5, segments); + this.arcTo(path, x + halfWidth - br, y + halfHeight - br, br, 0, Math.PI * 0.5, brSegments); // Bottom edge and bottom-left corner - path.push(x - halfWidth + radius, y + halfHeight); + path.push(x - halfWidth + bl, y + halfHeight); - this.arcTo(path, x - halfWidth + radius, y + halfHeight - radius, radius, Math.PI * 0.5, Math.PI, segments); + this.arcTo(path, x - halfWidth + bl, y + halfHeight - bl, bl, Math.PI * 0.5, Math.PI, blSegments); // Left edge (connects back to first point) - path.push(x - halfWidth, y - halfHeight + radius); + path.push(x - halfWidth, y - halfHeight + tl); this.pathIndexes = Earcut(path); this.pathData = path; From f30bb6b36e8b397d82f35899219e98a0c6a13228 Mon Sep 17 00:00:00 2001 From: ChrisCPI <92183829+ChrisCPI@users.noreply.github.com> Date: Wed, 28 May 2025 00:34:24 -0400 Subject: [PATCH 2/7] Update RectangleCanvasRenderer to support different radiuses for each corner --- .../rectangle/RectangleCanvasRenderer.js | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js b/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js index 6a1a6e919e..742abfe163 100644 --- a/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js +++ b/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js @@ -8,13 +8,16 @@ var FillStyleCanvas = require('../FillStyleCanvas'); var LineStyleCanvas = require('../LineStyleCanvas'); var SetTransform = require('../../../renderer/canvas/utils/SetTransform'); -var DrawRoundedRect = function (ctx, x, y, width, height, radius) +var DrawRoundedRect = function (ctx, x, y, width, height, tlRadius, trRadius, blRadius, brRadius) { // Limit radius to half of the smaller dimension var maxRadius = Math.min(width / 2, height / 2); - var r = Math.min(radius, maxRadius); + var tl = Math.min(tlRadius, maxRadius); + var tr = Math.min(trRadius, maxRadius); + var bl = Math.min(blRadius, maxRadius); + var br = Math.min(brRadius, maxRadius); - if (r === 0) + if (tl === 0 && tr === 0 && bl === 0 && br === 0) { // Fall back to normal rectangle if radius is 0 ctx.rect(x, y, width, height); @@ -22,23 +25,23 @@ var DrawRoundedRect = function (ctx, x, y, width, height, radius) } // Start at top-left, after the corner - ctx.moveTo(x + r, y); + ctx.moveTo(x + tl, y); // Top edge and top-right corner - ctx.lineTo(x + width - r, y); - ctx.arcTo(x + width, y, x + width, y + r, r); + ctx.lineTo(x + width - tr, y); + ctx.arcTo(x + width, y, x + width, y + tr, tr); // Right edge and bottom-right corner - ctx.lineTo(x + width, y + height - r); - ctx.arcTo(x + width, y + height, x + width - r, y + height, r); + ctx.lineTo(x + width, y + height - br); + ctx.arcTo(x + width, y + height, x + width - br, y + height, br); // Bottom edge and bottom-left corner - ctx.lineTo(x + r, y + height); - ctx.arcTo(x, y + height, x, y + height - r, r); + ctx.lineTo(x + bl, y + height); + ctx.arcTo(x, y + height, x, y + height - bl, bl); // Left edge and top-left corner - ctx.lineTo(x, y + r); - ctx.arcTo(x, y, x + r, y, r); + ctx.lineTo(x, y + tl); + ctx.arcTo(x, y, x + tl, y, tl); ctx.closePath(); }; @@ -75,7 +78,17 @@ var RectangleCanvasRenderer = function (renderer, src, camera, parentMatrix) if (src.isRounded) { ctx.beginPath(); - DrawRoundedRect(ctx, -dx, -dy, src.width, src.height, src.radius); + DrawRoundedRect( + ctx, + -dx, + -dy, + src.width, + src.height, + src.radiusTopLeft, + src.radiusTopRight, + src.radiusBottomLeft, + src.radiusBottomRight + ); ctx.fill(); } else @@ -97,7 +110,17 @@ var RectangleCanvasRenderer = function (renderer, src, camera, parentMatrix) if (src.isRounded) { - DrawRoundedRect(ctx, -dx, -dy, src.width, src.height, src.radius); + DrawRoundedRect( + ctx, + -dx, + -dy, + src.width, + src.height, + src.radiusTopLeft, + src.radiusTopRight, + src.radiusBottomLeft, + src.radiusBottomRight + ); } else { From e8914b0958e5aa562f9516bca3468bf0d1cbce38 Mon Sep 17 00:00:00 2001 From: ChrisCPI <92183829+ChrisCPI@users.noreply.github.com> Date: Wed, 28 May 2025 00:41:38 -0400 Subject: [PATCH 3/7] Fix misnamed properties --- src/gameobjects/shape/rectangle/Rectangle.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gameobjects/shape/rectangle/Rectangle.js b/src/gameobjects/shape/rectangle/Rectangle.js index 8bb2791ec9..463b2635c3 100644 --- a/src/gameobjects/shape/rectangle/Rectangle.js +++ b/src/gameobjects/shape/rectangle/Rectangle.js @@ -60,7 +60,7 @@ var Rectangle = new Class({ * Do not modify this property. Instead, call the method `setRounded` to set the * radius of the rounded corners. * - * @name Phaser.GameObjects.Rectangle#tintTopLeft + * @name Phaser.GameObjects.Rectangle#radiusTopLeft * @type {number} * @readonly */ @@ -72,7 +72,7 @@ var Rectangle = new Class({ * Do not modify this property. Instead, call the method `setRounded` to set the * radius of the rounded corners. * - * @name Phaser.GameObjects.Rectangle#tintTopLeft + * @name Phaser.GameObjects.Rectangle#radiusTopRight * @type {number} * @readonly */ @@ -84,7 +84,7 @@ var Rectangle = new Class({ * Do not modify this property. Instead, call the method `setRounded` to set the * radius of the rounded corners. * - * @name Phaser.GameObjects.Rectangle#tintTopLeft + * @name Phaser.GameObjects.Rectangle#radiusBottomLeft * @type {number} * @readonly */ @@ -96,7 +96,7 @@ var Rectangle = new Class({ * Do not modify this property. Instead, call the method `setRounded` to set the * radius of the rounded corners. * - * @name Phaser.GameObjects.Rectangle#tintTopLeft + * @name Phaser.GameObjects.Rectangle#radiusBottomRight * @type {number} * @readonly */ From 6804a9b502843c8ba5025d52e12b2f6d723ecb69 Mon Sep 17 00:00:00 2001 From: ChrisCPI <92183829+ChrisCPI@users.noreply.github.com> Date: Wed, 28 May 2025 16:40:26 -0400 Subject: [PATCH 4/7] For canvas renderer, don't draw arcs if the radius for that corner is 0 --- .../rectangle/RectangleCanvasRenderer.js | 80 ++++++++++++++----- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js b/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js index 742abfe163..504157aaba 100644 --- a/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js +++ b/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js @@ -23,26 +23,66 @@ var DrawRoundedRect = function (ctx, x, y, width, height, tlRadius, trRadius, bl ctx.rect(x, y, width, height); return; } - - // Start at top-left, after the corner - ctx.moveTo(x + tl, y); - - // Top edge and top-right corner - ctx.lineTo(x + width - tr, y); - ctx.arcTo(x + width, y, x + width, y + tr, tr); - - // Right edge and bottom-right corner - ctx.lineTo(x + width, y + height - br); - ctx.arcTo(x + width, y + height, x + width - br, y + height, br); - - // Bottom edge and bottom-left corner - ctx.lineTo(x + bl, y + height); - ctx.arcTo(x, y + height, x, y + height - bl, bl); - - // Left edge and top-left corner - ctx.lineTo(x, y + tl); - ctx.arcTo(x, y, x + tl, y, tl); - + + if (tl === 0) + { + // Start at top-left + ctx.moveTo(x, y); + } + else + { + // Start at top-left, after the corner + ctx.moveTo(x + tl, y); + } + + if (tr === 0) + { + // Top edge + ctx.lineTo(x + width, y); + } + else + { + // Top edge and top-right corner + ctx.lineTo(x + width - tr, y); + ctx.arcTo(x + width, y, x + width, y + tr, tr); + } + + if (br === 0) + { + // Right edge + ctx.lineTo(x + width, y + height); + } + else + { + // Right edge and bottom-right corner + ctx.lineTo(x + width, y + height - br); + ctx.arcTo(x + width, y + height, x + width - br, y + height, br); + } + + if (bl === 0) + { + // Bottom edge + ctx.lineTo(x, y + height); + } + else + { + // Bottom edge and bottom-left corner + ctx.lineTo(x + bl, y + height); + ctx.arcTo(x, y + height, x, y + height - bl, bl); + } + + if (tl === 0) + { + // Left edge + ctx.lineTo(x, y); + } + else + { + // Left edge and top-left corner + ctx.lineTo(x, y + tl); + ctx.arcTo(x, y, x + tl, y, tl); + } + ctx.closePath(); }; From 4eff136d65b805932cb5c1527c1bb48e8e485c34 Mon Sep 17 00:00:00 2001 From: ChrisCPI <92183829+ChrisCPI@users.noreply.github.com> Date: Wed, 28 May 2025 16:42:40 -0400 Subject: [PATCH 5/7] Fix trailing whitespace --- src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js b/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js index 504157aaba..b7fe2d5ad0 100644 --- a/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js +++ b/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js @@ -16,7 +16,7 @@ var DrawRoundedRect = function (ctx, x, y, width, height, tlRadius, trRadius, bl var tr = Math.min(trRadius, maxRadius); var bl = Math.min(blRadius, maxRadius); var br = Math.min(brRadius, maxRadius); - + if (tl === 0 && tr === 0 && bl === 0 && br === 0) { // Fall back to normal rectangle if radius is 0 From 54cab62f3887ff0d9ec6664f2033f8928ce21c27 Mon Sep 17 00:00:00 2001 From: ChrisCPI <92183829+ChrisCPI@users.noreply.github.com> Date: Thu, 29 May 2025 20:56:46 -0400 Subject: [PATCH 6/7] Update segment calculation to match https://github.com/phaserjs/phaser/commit/4ea4f95 --- src/gameobjects/shape/rectangle/Rectangle.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gameobjects/shape/rectangle/Rectangle.js b/src/gameobjects/shape/rectangle/Rectangle.js index 463b2635c3..4c3596c13e 100644 --- a/src/gameobjects/shape/rectangle/Rectangle.js +++ b/src/gameobjects/shape/rectangle/Rectangle.js @@ -300,10 +300,10 @@ var Rectangle = new Class({ var y = halfHeight; // The number of segments is based on radius (more segments = larger radius) - var tlSegments = Math.max(1, Math.floor(tl)); - var trSegments = Math.max(1, Math.floor(tr)); - var blSegments = Math.max(1, Math.floor(bl)); - var brSegments = Math.max(1, Math.floor(br)); + var tlSegments = Math.max(4, Math.min(16, Math.ceil(tl / 2))); + var trSegments = Math.max(4, Math.min(16, Math.ceil(tr / 2))); + var blSegments = Math.max(4, Math.min(16, Math.ceil(bl / 2))); + var brSegments = Math.max(4, Math.min(16, Math.ceil(br / 2))); // Create points going clockwise from top-left From 106860e5ec6ba45996b6d97945e9526eab0cf104 Mon Sep 17 00:00:00 2001 From: ChrisCPI <92183829+ChrisCPI@users.noreply.github.com> Date: Thu, 29 May 2025 21:09:10 -0400 Subject: [PATCH 7/7] Match the comment --- src/gameobjects/shape/rectangle/Rectangle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gameobjects/shape/rectangle/Rectangle.js b/src/gameobjects/shape/rectangle/Rectangle.js index 4c3596c13e..5191d5cada 100644 --- a/src/gameobjects/shape/rectangle/Rectangle.js +++ b/src/gameobjects/shape/rectangle/Rectangle.js @@ -299,7 +299,7 @@ var Rectangle = new Class({ var x = halfWidth; var y = halfHeight; - // The number of segments is based on radius (more segments = larger radius) + // Ensure minimum smoothness for small radii while preventing excessive tessellation var tlSegments = Math.max(4, Math.min(16, Math.ceil(tl / 2))); var trSegments = Math.max(4, Math.min(16, Math.ceil(tr / 2))); var blSegments = Math.max(4, Math.min(16, Math.ceil(bl / 2)));