import { Rectangle } from './Rectangle';
import type { ShapePrimitive } from './ShapePrimitive';
const isCornerWithinStroke = (
pX: number,
pY: number,
cornerX: number,
cornerY: number,
radius: number,
strokeWidthInner: number,
strokeWidthOuter: number
) =>
{
const dx = pX - cornerX;
const dy = pY - cornerY;
const distance = Math.sqrt((dx * dx) + (dy * dy));
return distance >= radius - strokeWidthInner && distance <= radius + strokeWidthOuter;
};
/**
* The `RoundedRectangle` object is an area defined by its position, as indicated by its top-left corner
* point (`x`, `y`) and by its `width` and its `height`, including a `radius` property that
* defines the radius of the rounded corners.
* @memberof maths
*/
export class RoundedRectangle implements ShapePrimitive
{
/**
* The X coordinate of the upper-left corner of the rounded rectangle
* @default 0
*/
public x: number;
/**
* The Y coordinate of the upper-left corner of the rounded rectangle
* @default 0
*/
public y: number;
/**
* The overall width of this rounded rectangle
* @default 0
*/
public width: number;
/**
* The overall height of this rounded rectangle
* @default 0
*/
public height: number;
/**
* Controls the radius of the rounded corners
* @default 20
*/
public radius: number;
/**
* The type of the object, mainly used to avoid `instanceof` checks
* @default 'roundedRectangle'
*/
public readonly type = 'roundedRectangle';
/**
* @param x - The X coordinate of the upper-left corner of the rounded rectangle
* @param y - The Y coordinate of the upper-left corner of the rounded rectangle
* @param width - The overall width of this rounded rectangle
* @param height - The overall height of this rounded rectangle
* @param radius - Controls the radius of the rounded corners
*/
constructor(x = 0, y = 0, width = 0, height = 0, radius = 20)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.radius = radius;
}
/**
* Returns the framing rectangle of the rounded rectangle as a Rectangle object
* @param out - optional rectangle to store the result
* @returns The framing rectangle
*/
public getBounds(out?: Rectangle): Rectangle
{
out ||= new Rectangle();
out.x = this.x;
out.y = this.y;
out.width = this.width;
out.height = this.height;
return out;
}
/**
* Creates a clone of this Rounded Rectangle.
* @returns - A copy of the rounded rectangle.
*/
public clone(): RoundedRectangle
{
return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius);
}
/**
* Copies another rectangle to this one.
* @param rectangle - The rectangle to copy from.
* @returns Returns itself.
*/
public copyFrom(rectangle: RoundedRectangle): this
{
this.x = rectangle.x;
this.y = rectangle.y;
this.width = rectangle.width;
this.height = rectangle.height;
return this;
}
/**
* Copies this rectangle to another one.
* @param rectangle - The rectangle to copy to.
* @returns Returns given parameter.
*/
public copyTo(rectangle: RoundedRectangle): RoundedRectangle
{
rectangle.copyFrom(this);
return rectangle;
}
/**
* Checks whether the x and y coordinates given are contained within this Rounded Rectangle
* @param x - The X coordinate of the point to test.
* @param y - The Y coordinate of the point to test.
* @returns - Whether the x/y coordinates are within this Rounded Rectangle.
*/
public contains(x: number, y: number): boolean
{
if (this.width <= 0 || this.height <= 0)
{
return false;
}
if (x >= this.x && x <= this.x + this.width)
{
if (y >= this.y && y <= this.y + this.height)
{
const radius = Math.max(0, Math.min(this.radius, Math.min(this.width, this.height) / 2));
if ((y >= this.y + radius && y <= this.y + this.height - radius)
|| (x >= this.x + radius && x <= this.x + this.width - radius))
{
return true;
}
let dx = x - (this.x + radius);
let dy = y - (this.y + radius);
const radius2 = radius * radius;
if ((dx * dx) + (dy * dy) <= radius2)
{
return true;
}
dx = x - (this.x + this.width - radius);
if ((dx * dx) + (dy * dy) <= radius2)
{
return true;
}
dy = y - (this.y + this.height - radius);
if ((dx * dx) + (dy * dy) <= radius2)
{
return true;
}
dx = x - (this.x + radius);
if ((dx * dx) + (dy * dy) <= radius2)
{
return true;
}
}
}
return false;
}
/**
* Checks whether the x and y coordinates given are contained within this rectangle including the stroke.
* @param pX - The X coordinate of the point to test
* @param pY - The Y coordinate of the point to test
* @param strokeWidth - The width of the line to check
* @param alignment - The alignment of the stroke, 0.5 by default
* @returns Whether the x/y coordinates are within this rectangle
*/
public strokeContains(pX: number, pY: number, strokeWidth: number, alignment: number = 0.5): boolean
{
const { x, y, width, height, radius } = this;
const strokeWidthOuter = strokeWidth * (1 - alignment);
const strokeWidthInner = strokeWidth - strokeWidthOuter;
const innerX = x + radius;
const innerY = y + radius;
const innerWidth = width - (radius * 2);
const innerHeight = height - (radius * 2);
const rightBound = x + width;
const bottomBound = y + height;
// Check if point is within the vertical edges (excluding corners)
if (((pX >= x - strokeWidthOuter && pX <= x + strokeWidthInner)
|| (pX >= rightBound - strokeWidthInner && pX <= rightBound + strokeWidthOuter))
&& pY >= innerY && pY <= innerY + innerHeight)
{
return true;
}
// Check if point is within the horizontal edges (excluding corners)
if (((pY >= y - strokeWidthOuter && pY <= y + strokeWidthInner)
|| (pY >= bottomBound - strokeWidthInner && pY <= bottomBound + strokeWidthOuter))
&& pX >= innerX && pX <= innerX + innerWidth)
{
return true;
}
// Top-left, top-right, bottom-right, bottom-left corners
return (
// Top-left
(pX < innerX && pY < innerY
&& isCornerWithinStroke(pX, pY, innerX, innerY,
radius, strokeWidthInner, strokeWidthOuter))
// top-right
|| (pX > rightBound - radius && pY < innerY
&& isCornerWithinStroke(pX, pY, rightBound - radius, innerY,
radius, strokeWidthInner, strokeWidthOuter))
// bottom-right
|| (pX > rightBound - radius && pY > bottomBound - radius
&& isCornerWithinStroke(pX, pY, rightBound - radius, bottomBound - radius,
radius, strokeWidthInner, strokeWidthOuter))
// bottom-left
|| (pX < innerX && pY > bottomBound - radius
&& isCornerWithinStroke(pX, pY, innerX, bottomBound - radius,
radius, strokeWidthInner, strokeWidthOuter)));
}
// #if _DEBUG
public toString(): string
{
return `[pixi.js/math:RoundedRectangle x=${this.x} y=${this.y}`
+ `width=${this.width} height=${this.height} radius=${this.radius}]`;
}
// #endif
}