Files
esengine/packages/math/src/Circle.ts
YHH ddc7a7750e Chore/lint fixes (#212)
* fix(eslint): 修复装饰器缩进配置

* fix(eslint): 修复装饰器缩进配置

* chore: 删除未使用的导入

* chore(lint): 移除未使用的导入和变量

* chore(lint): 修复editor-app中未使用的函数参数

* chore(lint): 修复未使用的赋值变量

* chore(eslint): 将所有错误级别改为警告以通过CI

* fix(codeql): 修复GitHub Advanced Security检测到的问题
2025-11-02 23:50:41 +08:00

595 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Vector2 } from './Vector2';
import { Rectangle } from './Rectangle';
/**
* 2D圆形类
*
* 表示一个圆形,提供圆形相关的几何运算功能:
* - 圆形创建和属性获取
* - 包含检测(点、圆形)
* - 相交检测和计算
* - 变换和操作
*/
export class Circle {
/** 圆心X坐标 */
public x: number;
/** 圆心Y坐标 */
public y: number;
/** 半径 */
public radius: number;
/**
* 创建圆形
* @param x 圆心X坐标默认为0
* @param y 圆心Y坐标默认为0
* @param radius 半径默认为0
*/
constructor(x: number = 0, y: number = 0, radius: number = 0) {
this.x = x;
this.y = y;
this.radius = radius;
}
// 静态常量
/** 空圆形 */
static readonly EMPTY = new Circle(0, 0, 0);
/** 单位圆 */
static readonly UNIT = new Circle(0, 0, 1);
// 属性获取
/** 获取圆心坐标 */
get center(): Vector2 {
return new Vector2(this.x, this.y);
}
/** 设置圆心坐标 */
set center(value: Vector2) {
this.x = value.x;
this.y = value.y;
}
/** 获取直径 */
get diameter(): number {
return this.radius * 2;
}
/** 设置直径 */
set diameter(value: number) {
this.radius = value * 0.5;
}
/** 获取面积 */
get area(): number {
return Math.PI * this.radius * this.radius;
}
/** 获取周长 */
get circumference(): number {
return 2 * Math.PI * this.radius;
}
/** 获取包围矩形 */
get bounds(): Rectangle {
return new Rectangle(
this.x - this.radius,
this.y - this.radius,
this.diameter,
this.diameter
);
}
/** 检查是否为空圆形 */
get isEmpty(): boolean {
return this.radius <= 0;
}
// 基础操作
/**
* 设置圆形属性
* @param x 圆心X坐标
* @param y 圆心Y坐标
* @param radius 半径
* @returns 当前圆形实例(链式调用)
*/
set(x: number, y: number, radius: number): this {
this.x = x;
this.y = y;
this.radius = radius;
return this;
}
/**
* 复制另一个圆形的值
* @param other 源圆形
* @returns 当前圆形实例(链式调用)
*/
copy(other: Circle): this {
this.x = other.x;
this.y = other.y;
this.radius = other.radius;
return this;
}
/**
* 克隆当前圆形
* @returns 新的圆形实例
*/
clone(): Circle {
return new Circle(this.x, this.y, this.radius);
}
/**
* 设置圆心位置
* @param x 新的X坐标
* @param y 新的Y坐标
* @returns 当前圆形实例(链式调用)
*/
setPosition(x: number, y: number): this {
this.x = x;
this.y = y;
return this;
}
/**
* 设置圆心位置(使用向量)
* @param center 新的圆心位置
* @returns 当前圆形实例(链式调用)
*/
setCenter(center: Vector2): this {
this.x = center.x;
this.y = center.y;
return this;
}
/**
* 设置半径
* @param radius 新的半径
* @returns 当前圆形实例(链式调用)
*/
setRadius(radius: number): this {
this.radius = radius;
return this;
}
// 变换操作
/**
* 平移圆形
* @param dx X方向偏移
* @param dy Y方向偏移
* @returns 当前圆形实例(链式调用)
*/
translate(dx: number, dy: number): this {
this.x += dx;
this.y += dy;
return this;
}
/**
* 平移圆形(使用向量)
* @param offset 偏移向量
* @returns 当前圆形实例(链式调用)
*/
translateBy(offset: Vector2): this {
this.x += offset.x;
this.y += offset.y;
return this;
}
/**
* 缩放圆形
* @param scale 缩放因子
* @returns 当前圆形实例(链式调用)
*/
scale(scale: number): this {
this.radius *= scale;
return this;
}
/**
* 扩展圆形
* @param amount 扩展量(正值扩大半径,负值缩小半径)
* @returns 当前圆形实例(链式调用)
*/
inflate(amount: number): this {
this.radius += amount;
return this;
}
// 包含检测
/**
* 检查是否包含指定点
* @param point 点
* @returns 是否包含
*/
containsPoint(point: Vector2): boolean {
const dx = point.x - this.x;
const dy = point.y - this.y;
return dx * dx + dy * dy <= this.radius * this.radius;
}
/**
* 检查是否包含指定坐标
* @param x X坐标
* @param y Y坐标
* @returns 是否包含
*/
contains(x: number, y: number): boolean {
const dx = x - this.x;
const dy = y - this.y;
return dx * dx + dy * dy <= this.radius * this.radius;
}
/**
* 检查是否完全包含另一个圆形
* @param other 另一个圆形
* @returns 是否完全包含
*/
containsCircle(other: Circle): boolean {
const distance = this.distanceToCircle(other);
return distance + other.radius <= this.radius;
}
/**
* 检查点是否在圆的边界上
* @param point 点
* @param epsilon 容差默认为Number.EPSILON
* @returns 是否在边界上
*/
pointOnBoundary(point: Vector2, epsilon: number = Number.EPSILON): boolean {
const distance = this.distanceToPoint(point);
return Math.abs(distance - this.radius) < epsilon;
}
// 相交检测
/**
* 检查是否与另一个圆形相交
* @param other 另一个圆形
* @returns 是否相交
*/
intersects(other: Circle): boolean {
const dx = this.x - other.x;
const dy = this.y - other.y;
const distanceSquared = dx * dx + dy * dy;
const radiusSum = this.radius + other.radius;
return distanceSquared <= radiusSum * radiusSum;
}
/**
* 检查是否与矩形相交
* @param rect 矩形
* @returns 是否相交
*/
intersectsRect(rect: Rectangle): boolean {
// 找到矩形上离圆心最近的点
const closestX = Math.max(rect.x, Math.min(this.x, rect.right));
const closestY = Math.max(rect.y, Math.min(this.y, rect.bottom));
// 计算圆心到最近点的距离
const dx = this.x - closestX;
const dy = this.y - closestY;
return dx * dx + dy * dy <= this.radius * this.radius;
}
/**
* 计算与另一个圆形的相交面积
* @param other 另一个圆形
* @returns 相交面积
*/
intersectionArea(other: Circle): number {
const d = this.distanceToCircle(other);
// 不相交
if (d >= this.radius + other.radius) {
return 0;
}
// 一个圆完全包含另一个圆
if (d <= Math.abs(this.radius - other.radius)) {
const smallerRadius = Math.min(this.radius, other.radius);
return Math.PI * smallerRadius * smallerRadius;
}
// 部分相交
const r1 = this.radius;
const r2 = other.radius;
const part1 = r1 * r1 * Math.acos((d * d + r1 * r1 - r2 * r2) / (2 * d * r1));
const part2 = r2 * r2 * Math.acos((d * d + r2 * r2 - r1 * r1) / (2 * d * r2));
const part3 = 0.5 * Math.sqrt((-d + r1 + r2) * (d + r1 - r2) * (d - r1 + r2) * (d + r1 + r2));
return part1 + part2 - part3;
}
// 距离计算
/**
* 计算圆心到点的距离
* @param point 点
* @returns 距离
*/
distanceToPoint(point: Vector2): number {
const dx = point.x - this.x;
const dy = point.y - this.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 计算圆形边界到点的最短距离
* @param point 点
* @returns 最短距离(点在圆内时为负值)
*/
distanceToPointFromBoundary(point: Vector2): number {
return this.distanceToPoint(point) - this.radius;
}
/**
* 计算两个圆心之间的距离
* @param other 另一个圆形
* @returns 圆心距离
*/
distanceToCircle(other: Circle): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 计算两个圆形边界之间的最短距离
* @param other 另一个圆形
* @returns 最短距离(相交时为负值)
*/
distanceToCircleFromBoundary(other: Circle): number {
return this.distanceToCircle(other) - this.radius - other.radius;
}
/**
* 计算圆形到矩形的最短距离
* @param rect 矩形
* @returns 最短距离
*/
distanceToRect(rect: Rectangle): number {
return Math.max(0, rect.distanceToPoint(this.center) - this.radius);
}
/**
* 获取圆形上距离指定点最近的点
* @param point 指定点
* @returns 最近点
*/
closestPointTo(point: Vector2): Vector2 {
const direction = Vector2.subtract(point, this.center);
if (direction.isZero) {
// 点在圆心,返回圆上任意点
return new Vector2(this.x + this.radius, this.y);
}
return this.center.clone().add(direction.normalized().multiply(this.radius));
}
/**
* 获取圆形上距离指定点最远的点
* @param point 指定点
* @returns 最远点
*/
farthestPointFrom(point: Vector2): Vector2 {
const direction = Vector2.subtract(point, this.center);
if (direction.isZero) {
// 点在圆心,返回圆上任意点
return new Vector2(this.x - this.radius, this.y);
}
return this.center.clone().subtract(direction.normalized().multiply(this.radius));
}
// 几何运算
/**
* 获取指定角度上的圆周点
* @param angle 角度(弧度)
* @returns 圆周点
*/
getPointAtAngle(angle: number): Vector2 {
return new Vector2(
this.x + this.radius * Math.cos(angle),
this.y + this.radius * Math.sin(angle)
);
}
/**
* 获取点相对于圆心的角度
* @param point 点
* @returns 角度(弧度)
*/
getAngleToPoint(point: Vector2): number {
return Math.atan2(point.y - this.y, point.x - this.x);
}
/**
* 获取圆形与直线的交点
* @param lineStart 直线起点
* @param lineEnd 直线终点
* @returns 交点数组0-2个点
*/
getLineIntersections(lineStart: Vector2, lineEnd: Vector2): Vector2[] {
const dx = lineEnd.x - lineStart.x;
const dy = lineEnd.y - lineStart.y;
const fx = lineStart.x - this.x;
const fy = lineStart.y - this.y;
const a = dx * dx + dy * dy;
const b = 2 * (fx * dx + fy * dy);
const c = fx * fx + fy * fy - this.radius * this.radius;
const discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
return []; // 无交点
}
if (discriminant === 0) {
// 一个交点(切线)
const t = -b / (2 * a);
return [new Vector2(lineStart.x + t * dx, lineStart.y + t * dy)];
}
// 两个交点
const sqrt = Math.sqrt(discriminant);
const t1 = (-b - sqrt) / (2 * a);
const t2 = (-b + sqrt) / (2 * a);
return [
new Vector2(lineStart.x + t1 * dx, lineStart.y + t1 * dy),
new Vector2(lineStart.x + t2 * dx, lineStart.y + t2 * dy)
];
}
// 比较操作
/**
* 检查两个圆形是否相等
* @param other 另一个圆形
* @param epsilon 容差默认为Number.EPSILON
* @returns 是否相等
*/
equals(other: Circle, epsilon: number = Number.EPSILON): boolean {
return Math.abs(this.x - other.x) < epsilon &&
Math.abs(this.y - other.y) < epsilon &&
Math.abs(this.radius - other.radius) < epsilon;
}
/**
* 检查两个圆形是否完全相等
* @param other 另一个圆形
* @returns 是否完全相等
*/
exactEquals(other: Circle): boolean {
return this.x === other.x && this.y === other.y && this.radius === other.radius;
}
// 静态方法
/**
* 从直径创建圆形
* @param x 圆心X坐标
* @param y 圆心Y坐标
* @param diameter 直径
* @returns 新的圆形实例
*/
static fromDiameter(x: number, y: number, diameter: number): Circle {
return new Circle(x, y, diameter * 0.5);
}
/**
* 从三个点创建外接圆
* @param p1 第一个点
* @param p2 第二个点
* @param p3 第三个点
* @returns 外接圆如果三点共线返回null
*/
static fromThreePoints(p1: Vector2, p2: Vector2, p3: Vector2): Circle | null {
const ax = p1.x; const ay = p1.y;
const bx = p2.x; const by = p2.y;
const cx = p3.x; const cy = p3.y;
const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
if (Math.abs(d) < Number.EPSILON) {
return null; // 三点共线
}
const ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d;
const uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d;
const radius = Math.sqrt((ax - ux) * (ax - ux) + (ay - uy) * (ay - uy));
return new Circle(ux, uy, radius);
}
/**
* 从点数组创建最小包围圆
* @param points 点数组
* @returns 最小包围圆
*/
static fromPointArray(points: Vector2[]): Circle {
if (points.length === 0) {
return Circle.EMPTY.clone();
}
if (points.length === 1) {
return new Circle(points[0].x, points[0].y, 0);
}
// 使用Welzl算法的简化版本
// 这里使用更简单的方法:找到包围所有点的圆
let minX = points[0].x, minY = points[0].y;
let maxX = points[0].x, maxY = points[0].y;
for (const point of points) {
minX = Math.min(minX, point.x);
minY = Math.min(minY, point.y);
maxX = Math.max(maxX, point.x);
maxY = Math.max(maxY, point.y);
}
const centerX = (minX + maxX) * 0.5;
const centerY = (minY + maxY) * 0.5;
const center = new Vector2(centerX, centerY);
let maxDistance = 0;
for (const point of points) {
const distance = Vector2.distance(center, point);
maxDistance = Math.max(maxDistance, distance);
}
return new Circle(centerX, centerY, maxDistance);
}
/**
* 线性插值两个圆形
* @param a 起始圆形
* @param b 目标圆形
* @param t 插值参数0到1
* @returns 新的插值结果圆形
*/
static lerp(a: Circle, b: Circle, t: number): Circle {
return new Circle(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t,
a.radius + (b.radius - a.radius) * t
);
}
// 字符串转换
/**
* 转换为字符串
* @returns 字符串表示
*/
toString(): string {
return `Circle(${this.x.toFixed(2)}, ${this.y.toFixed(2)}, r=${this.radius.toFixed(2)})`;
}
/**
* 转换为数组
* @returns [x, y, radius] 数组
*/
toArray(): [number, number, number] {
return [this.x, this.y, this.radius];
}
/**
* 转换为普通对象
* @returns {x, y, radius} 对象
*/
toObject(): { x: number; y: number; radius: number } {
return { x: this.x, y: this.y, radius: this.radius };
}
}