2025-08-10 16:00:02 +08:00
|
|
|
|
import { Vector2 } from './Vector2';
|
|
|
|
|
|
import { Rectangle } from './Rectangle';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 2D圆形类
|
2025-11-02 23:50:41 +08:00
|
|
|
|
*
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 表示一个圆形,提供圆形相关的几何运算功能:
|
|
|
|
|
|
* - 圆形创建和属性获取
|
|
|
|
|
|
* - 包含检测(点、圆形)
|
|
|
|
|
|
* - 相交检测和计算
|
|
|
|
|
|
* - 变换和操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class Circle {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/** 圆心X坐标 */
|
|
|
|
|
|
public x: number;
|
|
|
|
|
|
|
|
|
|
|
|
/** 圆心Y坐标 */
|
|
|
|
|
|
public y: number;
|
|
|
|
|
|
|
|
|
|
|
|
/** 半径 */
|
|
|
|
|
|
public radius: number;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 创建圆形
|
|
|
|
|
|
* @param x 圆心X坐标,默认为0
|
|
|
|
|
|
* @param y 圆心Y坐标,默认为0
|
|
|
|
|
|
* @param radius 半径,默认为0
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 基础操作
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 设置圆形属性
|
|
|
|
|
|
* @param x 圆心X坐标
|
|
|
|
|
|
* @param y 圆心Y坐标
|
|
|
|
|
|
* @param radius 半径
|
|
|
|
|
|
* @returns 当前圆形实例(链式调用)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
set(x: number, y: number, radius: number): this {
|
|
|
|
|
|
this.x = x;
|
|
|
|
|
|
this.y = y;
|
|
|
|
|
|
this.radius = radius;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 复制另一个圆形的值
|
|
|
|
|
|
* @param other 源圆形
|
|
|
|
|
|
* @returns 当前圆形实例(链式调用)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
copy(other: Circle): this {
|
|
|
|
|
|
this.x = other.x;
|
|
|
|
|
|
this.y = other.y;
|
|
|
|
|
|
this.radius = other.radius;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 克隆当前圆形
|
|
|
|
|
|
* @returns 新的圆形实例
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
clone(): Circle {
|
|
|
|
|
|
return new Circle(this.x, this.y, this.radius);
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 设置圆心位置
|
|
|
|
|
|
* @param x 新的X坐标
|
|
|
|
|
|
* @param y 新的Y坐标
|
|
|
|
|
|
* @returns 当前圆形实例(链式调用)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setPosition(x: number, y: number): this {
|
|
|
|
|
|
this.x = x;
|
|
|
|
|
|
this.y = y;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 设置圆心位置(使用向量)
|
|
|
|
|
|
* @param center 新的圆心位置
|
|
|
|
|
|
* @returns 当前圆形实例(链式调用)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setCenter(center: Vector2): this {
|
|
|
|
|
|
this.x = center.x;
|
|
|
|
|
|
this.y = center.y;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 设置半径
|
|
|
|
|
|
* @param radius 新的半径
|
|
|
|
|
|
* @returns 当前圆形实例(链式调用)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setRadius(radius: number): this {
|
|
|
|
|
|
this.radius = radius;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
// 变换操作
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 平移圆形
|
|
|
|
|
|
* @param dx X方向偏移
|
|
|
|
|
|
* @param dy Y方向偏移
|
|
|
|
|
|
* @returns 当前圆形实例(链式调用)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
translate(dx: number, dy: number): this {
|
|
|
|
|
|
this.x += dx;
|
|
|
|
|
|
this.y += dy;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 平移圆形(使用向量)
|
|
|
|
|
|
* @param offset 偏移向量
|
|
|
|
|
|
* @returns 当前圆形实例(链式调用)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
translateBy(offset: Vector2): this {
|
|
|
|
|
|
this.x += offset.x;
|
|
|
|
|
|
this.y += offset.y;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 缩放圆形
|
|
|
|
|
|
* @param scale 缩放因子
|
|
|
|
|
|
* @returns 当前圆形实例(链式调用)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
scale(scale: number): this {
|
|
|
|
|
|
this.radius *= scale;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 扩展圆形
|
|
|
|
|
|
* @param amount 扩展量(正值扩大半径,负值缩小半径)
|
|
|
|
|
|
* @returns 当前圆形实例(链式调用)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
inflate(amount: number): this {
|
|
|
|
|
|
this.radius += amount;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
// 包含检测
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 检查是否包含指定点
|
|
|
|
|
|
* @param point 点
|
|
|
|
|
|
* @returns 是否包含
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 检查是否包含指定坐标
|
|
|
|
|
|
* @param x X坐标
|
|
|
|
|
|
* @param y Y坐标
|
|
|
|
|
|
* @returns 是否包含
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 检查是否完全包含另一个圆形
|
|
|
|
|
|
* @param other 另一个圆形
|
|
|
|
|
|
* @returns 是否完全包含
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
containsCircle(other: Circle): boolean {
|
|
|
|
|
|
const distance = this.distanceToCircle(other);
|
|
|
|
|
|
return distance + other.radius <= this.radius;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 检查点是否在圆的边界上
|
|
|
|
|
|
* @param point 点
|
|
|
|
|
|
* @param epsilon 容差,默认为Number.EPSILON
|
|
|
|
|
|
* @returns 是否在边界上
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
pointOnBoundary(point: Vector2, epsilon: number = Number.EPSILON): boolean {
|
|
|
|
|
|
const distance = this.distanceToPoint(point);
|
|
|
|
|
|
return Math.abs(distance - this.radius) < epsilon;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
// 相交检测
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 检查是否与另一个圆形相交
|
|
|
|
|
|
* @param other 另一个圆形
|
|
|
|
|
|
* @returns 是否相交
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 检查是否与矩形相交
|
|
|
|
|
|
* @param rect 矩形
|
|
|
|
|
|
* @returns 是否相交
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
intersectsRect(rect: Rectangle): boolean {
|
2025-08-10 16:00:02 +08:00
|
|
|
|
// 找到矩形上离圆心最近的点
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 计算与另一个圆形的相交面积
|
|
|
|
|
|
* @param other 另一个圆形
|
|
|
|
|
|
* @returns 相交面积
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 距离计算
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 计算圆心到点的距离
|
|
|
|
|
|
* @param point 点
|
|
|
|
|
|
* @returns 距离
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
distanceToPoint(point: Vector2): number {
|
|
|
|
|
|
const dx = point.x - this.x;
|
|
|
|
|
|
const dy = point.y - this.y;
|
|
|
|
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 计算圆形边界到点的最短距离
|
|
|
|
|
|
* @param point 点
|
|
|
|
|
|
* @returns 最短距离(点在圆内时为负值)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
distanceToPointFromBoundary(point: Vector2): number {
|
|
|
|
|
|
return this.distanceToPoint(point) - this.radius;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 计算两个圆心之间的距离
|
|
|
|
|
|
* @param other 另一个圆形
|
|
|
|
|
|
* @returns 圆心距离
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
distanceToCircle(other: Circle): number {
|
|
|
|
|
|
const dx = this.x - other.x;
|
|
|
|
|
|
const dy = this.y - other.y;
|
|
|
|
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 计算两个圆形边界之间的最短距离
|
|
|
|
|
|
* @param other 另一个圆形
|
|
|
|
|
|
* @returns 最短距离(相交时为负值)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
distanceToCircleFromBoundary(other: Circle): number {
|
|
|
|
|
|
return this.distanceToCircle(other) - this.radius - other.radius;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 计算圆形到矩形的最短距离
|
|
|
|
|
|
* @param rect 矩形
|
|
|
|
|
|
* @returns 最短距离
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
distanceToRect(rect: Rectangle): number {
|
|
|
|
|
|
return Math.max(0, rect.distanceToPoint(this.center) - this.radius);
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 获取圆形上距离指定点最近的点
|
|
|
|
|
|
* @param point 指定点
|
|
|
|
|
|
* @returns 最近点
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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));
|
2025-08-10 16:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 获取圆形上距离指定点最远的点
|
|
|
|
|
|
* @param point 指定点
|
|
|
|
|
|
* @returns 最远点
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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));
|
2025-08-10 16:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
// 几何运算
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 获取指定角度上的圆周点
|
|
|
|
|
|
* @param angle 角度(弧度)
|
|
|
|
|
|
* @returns 圆周点
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
getPointAtAngle(angle: number): Vector2 {
|
|
|
|
|
|
return new Vector2(
|
|
|
|
|
|
this.x + this.radius * Math.cos(angle),
|
|
|
|
|
|
this.y + this.radius * Math.sin(angle)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 获取点相对于圆心的角度
|
|
|
|
|
|
* @param point 点
|
|
|
|
|
|
* @returns 角度(弧度)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
getAngleToPoint(point: Vector2): number {
|
|
|
|
|
|
return Math.atan2(point.y - this.y, point.x - this.x);
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 获取圆形与直线的交点
|
|
|
|
|
|
* @param lineStart 直线起点
|
|
|
|
|
|
* @param lineEnd 直线终点
|
|
|
|
|
|
* @returns 交点数组(0-2个点)
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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)
|
|
|
|
|
|
];
|
2025-08-10 16:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
// 比较操作
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 检查两个圆形是否相等
|
|
|
|
|
|
* @param other 另一个圆形
|
|
|
|
|
|
* @param epsilon 容差,默认为Number.EPSILON
|
|
|
|
|
|
* @returns 是否相等
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
equals(other: Circle, epsilon: number = Number.EPSILON): boolean {
|
|
|
|
|
|
return Math.abs(this.x - other.x) < epsilon &&
|
2025-08-10 16:00:02 +08:00
|
|
|
|
Math.abs(this.y - other.y) < epsilon &&
|
|
|
|
|
|
Math.abs(this.radius - other.radius) < epsilon;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 检查两个圆形是否完全相等
|
|
|
|
|
|
* @param other 另一个圆形
|
|
|
|
|
|
* @returns 是否完全相等
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
exactEquals(other: Circle): boolean {
|
|
|
|
|
|
return this.x === other.x && this.y === other.y && this.radius === other.radius;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
// 静态方法
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 从直径创建圆形
|
|
|
|
|
|
* @param x 圆心X坐标
|
|
|
|
|
|
* @param y 圆心Y坐标
|
|
|
|
|
|
* @param diameter 直径
|
|
|
|
|
|
* @returns 新的圆形实例
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
static fromDiameter(x: number, y: number, diameter: number): Circle {
|
|
|
|
|
|
return new Circle(x, y, diameter * 0.5);
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 从三个点创建外接圆
|
|
|
|
|
|
* @param p1 第一个点
|
|
|
|
|
|
* @param p2 第二个点
|
|
|
|
|
|
* @param p3 第三个点
|
|
|
|
|
|
* @returns 外接圆,如果三点共线返回null
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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;
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
|
|
|
|
|
|
|
|
|
|
|
|
if (Math.abs(d) < Number.EPSILON) {
|
|
|
|
|
|
return null; // 三点共线
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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;
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const radius = Math.sqrt((ax - ux) * (ax - ux) + (ay - uy) * (ay - uy));
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
return new Circle(ux, uy, radius);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 从点数组创建最小包围圆
|
|
|
|
|
|
* @param points 点数组
|
|
|
|
|
|
* @returns 最小包围圆
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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);
|
2025-08-10 16:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 线性插值两个圆形
|
|
|
|
|
|
* @param a 起始圆形
|
|
|
|
|
|
* @param b 目标圆形
|
|
|
|
|
|
* @param t 插值参数(0到1)
|
|
|
|
|
|
* @returns 新的插值结果圆形
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
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
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
// 字符串转换
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 转换为字符串
|
|
|
|
|
|
* @returns 字符串表示
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
toString(): string {
|
|
|
|
|
|
return `Circle(${this.x.toFixed(2)}, ${this.y.toFixed(2)}, r=${this.radius.toFixed(2)})`;
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 转换为数组
|
|
|
|
|
|
* @returns [x, y, radius] 数组
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
toArray(): [number, number, number] {
|
|
|
|
|
|
return [this.x, this.y, this.radius];
|
|
|
|
|
|
}
|
2025-08-10 16:00:02 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/**
|
2025-08-10 16:00:02 +08:00
|
|
|
|
* 转换为普通对象
|
|
|
|
|
|
* @returns {x, y, radius} 对象
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
toObject(): { x: number; y: number; radius: number } {
|
|
|
|
|
|
return { x: this.x, y: this.y, radius: this.radius };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|