Files
esengine/packages/math/src/Rectangle.ts

520 lines
12 KiB
TypeScript
Raw Normal View History

2025-08-10 16:00:02 +08:00
import { Vector2 } from './Vector2';
/**
* 2D矩形类
*
*
* -
* -
* -
* -
*/
export class Rectangle {
/** 矩形左上角X坐标 */
public x: number;
/** 矩形左上角Y坐标 */
public y: number;
/** 矩形宽度 */
public width: number;
/** 矩形高度 */
public height: number;
/**
*
* @param x X坐标0
* @param y Y坐标0
* @param width 0
* @param height 0
*/
constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
// 静态常量
/** 空矩形 */
static readonly EMPTY = new Rectangle(0, 0, 0, 0);
// 属性获取
/** 获取左边界 */
get left(): number {
return this.x;
}
/** 获取右边界 */
get right(): number {
return this.x + this.width;
}
/** 获取上边界 */
get top(): number {
return this.y;
}
/** 获取下边界 */
get bottom(): number {
return this.y + this.height;
}
/** 获取中心X坐标 */
get centerX(): number {
return this.x + this.width * 0.5;
}
/** 获取中心Y坐标 */
get centerY(): number {
return this.y + this.height * 0.5;
}
/** 获取中心点 */
get center(): Vector2 {
return new Vector2(this.centerX, this.centerY);
}
/** 获取左上角点 */
get topLeft(): Vector2 {
return new Vector2(this.x, this.y);
}
/** 获取右上角点 */
get topRight(): Vector2 {
return new Vector2(this.right, this.y);
}
/** 获取左下角点 */
get bottomLeft(): Vector2 {
return new Vector2(this.x, this.bottom);
}
/** 获取右下角点 */
get bottomRight(): Vector2 {
return new Vector2(this.right, this.bottom);
}
/** 获取面积 */
get area(): number {
return this.width * this.height;
}
/** 获取周长 */
get perimeter(): number {
return 2 * (this.width + this.height);
}
/** 检查是否为空矩形 */
get isEmpty(): boolean {
return this.width <= 0 || this.height <= 0;
}
/** 检查是否为正方形 */
get isSquare(): boolean {
return this.width === this.height && this.width > 0;
}
// 基础操作
/**
*
* @param x X坐标
* @param y Y坐标
* @param width
* @param height
* @returns
*/
set(x: number, y: number, width: number, height: number): this {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
return this;
}
/**
*
* @param other
* @returns
*/
copy(other: Rectangle): this {
this.x = other.x;
this.y = other.y;
this.width = other.width;
this.height = other.height;
return this;
}
/**
*
* @returns
*/
clone(): Rectangle {
return new Rectangle(this.x, this.y, this.width, this.height);
}
/**
*
* @param x X坐标
* @param y Y坐标
* @returns
*/
setPosition(x: number, y: number): this {
this.x = x;
this.y = y;
return this;
}
/**
*
* @param width
* @param height
* @returns
*/
setSize(width: number, height: number): this {
this.width = width;
this.height = height;
return this;
}
/**
*
* @param centerX X坐标
* @param centerY Y坐标
* @returns
*/
setCenter(centerX: number, centerY: number): this {
this.x = centerX - this.width * 0.5;
this.y = centerY - this.height * 0.5;
return this;
}
// 变换操作
/**
*
* @param dx X方向偏移
* @param dy Y方向偏移
* @returns
*/
translate(dx: number, dy: number): this {
this.x += dx;
this.y += dy;
return this;
}
/**
*
* @param scaleX X方向缩放因子
* @param scaleY Y方向缩放因子scaleX
* @returns
*/
scale(scaleX: number, scaleY: number = scaleX): this {
const centerX = this.centerX;
const centerY = this.centerY;
this.width *= scaleX;
this.height *= scaleY;
return this.setCenter(centerX, centerY);
}
/**
*
* @param amount
* @returns
*/
inflate(amount: number): this {
this.x -= amount;
this.y -= amount;
this.width += amount * 2;
this.height += amount * 2;
return this;
}
/**
*
* @param horizontal
* @param vertical
* @returns
*/
inflateXY(horizontal: number, vertical: number): this {
this.x -= horizontal;
this.y -= vertical;
this.width += horizontal * 2;
this.height += vertical * 2;
return this;
}
// 包含检测
/**
*
* @param point
* @returns
*/
containsPoint(point: Vector2): boolean {
return point.x >= this.x && point.x <= this.right &&
point.y >= this.y && point.y <= this.bottom;
}
/**
*
* @param x X坐标
* @param y Y坐标
* @returns
*/
contains(x: number, y: number): boolean {
return x >= this.x && x <= this.right &&
y >= this.y && y <= this.bottom;
}
/**
*
* @param other
* @returns
*/
containsRect(other: Rectangle): boolean {
return this.x <= other.x && this.y <= other.y &&
this.right >= other.right && this.bottom >= other.bottom;
}
// 相交检测
/**
*
* @param other
* @returns
*/
intersects(other: Rectangle): boolean {
return this.x < other.right && this.right > other.x &&
this.y < other.bottom && this.bottom > other.y;
}
/**
*
* @param other
* @returns
*/
intersection(other: Rectangle): Rectangle {
if (!this.intersects(other)) {
return Rectangle.EMPTY.clone();
}
const x = Math.max(this.x, other.x);
const y = Math.max(this.y, other.y);
const right = Math.min(this.right, other.right);
const bottom = Math.min(this.bottom, other.bottom);
return new Rectangle(x, y, right - x, bottom - y);
}
/**
*
* @param other
* @returns
*/
union(other: Rectangle): Rectangle {
const x = Math.min(this.x, other.x);
const y = Math.min(this.y, other.y);
const right = Math.max(this.right, other.right);
const bottom = Math.max(this.bottom, other.bottom);
return new Rectangle(x, y, right - x, bottom - y);
}
/**
*
* @param other
* @returns
*/
intersectionArea(other: Rectangle): number {
const intersection = this.intersection(other);
return intersection.isEmpty ? 0 : intersection.area;
}
// 距离计算
/**
*
* @param point
* @returns
*/
distanceToPoint(point: Vector2): number {
const dx = Math.max(0, Math.max(this.x - point.x, point.x - this.right));
const dy = Math.max(0, Math.max(this.y - point.y, point.y - this.bottom));
return Math.sqrt(dx * dx + dy * dy);
}
/**
*
* @param other
* @returns 0
*/
distanceToRect(other: Rectangle): number {
if (this.intersects(other)) {
return 0;
}
const dx = Math.max(0, Math.max(this.x - other.right, other.x - this.right));
const dy = Math.max(0, Math.max(this.y - other.bottom, other.y - this.bottom));
return Math.sqrt(dx * dx + dy * dy);
}
/**
*
* @param point
* @returns
*/
closestPointTo(point: Vector2): Vector2 {
return new Vector2(
Math.max(this.x, Math.min(this.right, point.x)),
Math.max(this.y, Math.min(this.bottom, point.y))
);
}
// 比较操作
/**
*
* @param other
* @param epsilon Number.EPSILON
* @returns
*/
equals(other: Rectangle, epsilon: number = Number.EPSILON): boolean {
return Math.abs(this.x - other.x) < epsilon &&
Math.abs(this.y - other.y) < epsilon &&
Math.abs(this.width - other.width) < epsilon &&
Math.abs(this.height - other.height) < epsilon;
}
/**
*
* @param other
* @returns
*/
exactEquals(other: Rectangle): boolean {
return this.x === other.x && this.y === other.y &&
this.width === other.width && this.height === other.height;
}
// 静态方法
/**
*
* @param centerX X坐标
* @param centerY Y坐标
* @param width
* @param height
* @returns
*/
static fromCenter(centerX: number, centerY: number, width: number, height: number): Rectangle {
return new Rectangle(centerX - width * 0.5, centerY - height * 0.5, width, height);
}
/**
*
* @param point1
* @param point2
* @returns
*/
static fromPoints(point1: Vector2, point2: Vector2): Rectangle {
const x = Math.min(point1.x, point2.x);
const y = Math.min(point1.y, point2.y);
const width = Math.abs(point2.x - point1.x);
const height = Math.abs(point2.y - point1.y);
return new Rectangle(x, y, width, height);
}
/**
*
* @param points
* @returns
*/
static fromPointArray(points: Vector2[]): Rectangle {
if (points.length === 0) {
return Rectangle.EMPTY.clone();
}
let minX = points[0].x;
let minY = points[0].y;
let maxX = points[0].x;
let maxY = points[0].y;
for (let i = 1; i < points.length; i++) {
minX = Math.min(minX, points[i].x);
minY = Math.min(minY, points[i].y);
maxX = Math.max(maxX, points[i].x);
maxY = Math.max(maxY, points[i].y);
}
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
/**
*
* @param x X坐标
* @param y Y坐标
* @param size
* @returns
*/
static square(x: number, y: number, size: number): Rectangle {
return new Rectangle(x, y, size, size);
}
/**
* 线
* @param a
* @param b
* @param t 01
* @returns
*/
static lerp(a: Rectangle, b: Rectangle, t: number): Rectangle {
return new Rectangle(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t,
a.width + (b.width - a.width) * t,
a.height + (b.height - a.height) * t
);
}
// 字符串转换
/**
*
* @returns
*/
toString(): string {
return `Rectangle(${this.x.toFixed(2)}, ${this.y.toFixed(2)}, ${this.width.toFixed(2)}, ${this.height.toFixed(2)})`;
}
/**
*
* @returns [x, y, width, height]
*/
toArray(): [number, number, number, number] {
return [this.x, this.y, this.width, this.height];
}
/**
*
* @returns {x, y, width, height}
*/
toObject(): { x: number; y: number; width: number; height: number } {
return { x: this.x, y: this.y, width: this.width, height: this.height };
}
/**
*
* @returns [topLeft, topRight, bottomRight, bottomLeft]
*/
getVertices(): Vector2[] {
return [
this.topLeft,
this.topRight,
this.bottomRight,
this.bottomLeft
];
}
}