520 lines
12 KiB
TypeScript
520 lines
12 KiB
TypeScript
|
|
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 插值参数(0到1)
|
|||
|
|
* @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
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
}
|