refactor: reorganize package structure and decouple framework packages (#338)

* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
YHH
2025-12-26 14:50:35 +08:00
committed by GitHub
parent a84ff902e4
commit 155411e743
1936 changed files with 4147 additions and 11578 deletions

View File

@@ -0,0 +1,464 @@
/**
* 缓动函数集合
*
* 提供各种常用的缓动函数,用于创建平滑的动画效果
* 所有函数接受时间参数 t (0-1),返回缓动后的值 (通常0-1)
*/
export class Easing {
// 线性缓动
/**
* 线性缓动(无缓动)
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static linear(t: number): number {
return t;
}
// 二次方缓动 (Quadratic)
/**
* 二次方缓入
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static quadIn(t: number): number {
return t * t;
}
/**
* 二次方缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static quadOut(t: number): number {
return 1 - (1 - t) * (1 - t);
}
/**
* 二次方缓入缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static quadInOut(t: number): number {
return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
}
// 三次方缓动 (Cubic)
/**
* 三次方缓入
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static cubicIn(t: number): number {
return t * t * t;
}
/**
* 三次方缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static cubicOut(t: number): number {
return 1 - Math.pow(1 - t, 3);
}
/**
* 三次方缓入缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static cubicInOut(t: number): number {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
// 四次方缓动 (Quartic)
/**
* 四次方缓入
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static quartIn(t: number): number {
return t * t * t * t;
}
/**
* 四次方缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static quartOut(t: number): number {
return 1 - Math.pow(1 - t, 4);
}
/**
* 四次方缓入缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static quartInOut(t: number): number {
return t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
}
// 五次方缓动 (Quintic)
/**
* 五次方缓入
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static quintIn(t: number): number {
return t * t * t * t * t;
}
/**
* 五次方缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static quintOut(t: number): number {
return 1 - Math.pow(1 - t, 5);
}
/**
* 五次方缓入缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static quintInOut(t: number): number {
return t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2;
}
// 正弦缓动 (Sine)
/**
* 正弦缓入
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static sineIn(t: number): number {
return 1 - Math.cos((t * Math.PI) / 2);
}
/**
* 正弦缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static sineOut(t: number): number {
return Math.sin((t * Math.PI) / 2);
}
/**
* 正弦缓入缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static sineInOut(t: number): number {
return -(Math.cos(Math.PI * t) - 1) / 2;
}
// 指数缓动 (Exponential)
/**
* 指数缓入
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static expoIn(t: number): number {
return t === 0 ? 0 : Math.pow(2, 10 * (t - 1));
}
/**
* 指数缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static expoOut(t: number): number {
return t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
}
/**
* 指数缓入缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static expoInOut(t: number): number {
if (t === 0) return 0;
if (t === 1) return 1;
return t < 0.5
? Math.pow(2, 20 * t - 10) / 2
: (2 - Math.pow(2, -20 * t + 10)) / 2;
}
// 圆形缓动 (Circular)
/**
* 圆形缓入
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static circIn(t: number): number {
return 1 - Math.sqrt(1 - t * t);
}
/**
* 圆形缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static circOut(t: number): number {
return Math.sqrt(1 - (t - 1) * (t - 1));
}
/**
* 圆形缓入缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static circInOut(t: number): number {
return t < 0.5
? (1 - Math.sqrt(1 - 4 * t * t)) / 2
: (Math.sqrt(1 - (-2 * t + 2) * (-2 * t + 2)) + 1) / 2;
}
// 回弹缓动 (Back)
/**
* 回弹缓入
* @param t 时间参数 (0-1)
* @param s 回弹强度默认1.70158
* @returns 缓动值
*/
static backIn(t: number, s: number = 1.70158): number {
const c1 = s;
const c3 = c1 + 1;
return c3 * t * t * t - c1 * t * t;
}
/**
* 回弹缓出
* @param t 时间参数 (0-1)
* @param s 回弹强度默认1.70158
* @returns 缓动值
*/
static backOut(t: number, s: number = 1.70158): number {
const c1 = s;
const c3 = c1 + 1;
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
}
/**
* 回弹缓入缓出
* @param t 时间参数 (0-1)
* @param s 回弹强度默认1.70158
* @returns 缓动值
*/
static backInOut(t: number, s: number = 1.70158): number {
const c1 = s;
const c2 = c1 * 1.525;
return t < 0.5
? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
: (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
}
// 弹性缓动 (Elastic)
/**
* 弹性缓入
* @param t 时间参数 (0-1)
* @param amplitude 振幅默认1
* @param period 周期默认0.3
* @returns 缓动值
*/
static elasticIn(t: number, amplitude: number = 1, period: number = 0.3): number {
if (t === 0) return 0;
if (t === 1) return 1;
const s = period / 4;
return -(amplitude * Math.pow(2, 10 * (t - 1)) * Math.sin((t - 1 - s) * (2 * Math.PI) / period));
}
/**
* 弹性缓出
* @param t 时间参数 (0-1)
* @param amplitude 振幅默认1
* @param period 周期默认0.3
* @returns 缓动值
*/
static elasticOut(t: number, amplitude: number = 1, period: number = 0.3): number {
if (t === 0) return 0;
if (t === 1) return 1;
const s = period / 4;
return amplitude * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / period) + 1;
}
/**
* 弹性缓入缓出
* @param t 时间参数 (0-1)
* @param amplitude 振幅默认1
* @param period 周期默认0.45
* @returns 缓动值
*/
static elasticInOut(t: number, amplitude: number = 1, period: number = 0.45): number {
if (t === 0) return 0;
if (t === 1) return 1;
const s = period / 4;
if (t < 0.5) {
return -0.5 * (amplitude * Math.pow(2, 10 * (2 * t - 1)) * Math.sin((2 * t - 1 - s) * (2 * Math.PI) / period));
}
return amplitude * Math.pow(2, -10 * (2 * t - 1)) * Math.sin((2 * t - 1 - s) * (2 * Math.PI) / period) * 0.5 + 1;
}
// 跳跃缓动 (Bounce)
/**
* 跳跃缓入
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static bounceIn(t: number): number {
return 1 - Easing.bounceOut(1 - t);
}
/**
* 跳跃缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static bounceOut(t: number): number {
const n1 = 7.5625;
const d1 = 2.75;
if (t < 1 / d1) {
return n1 * t * t;
} else if (t < 2 / d1) {
return n1 * (t -= 1.5 / d1) * t + 0.75;
} else if (t < 2.5 / d1) {
return n1 * (t -= 2.25 / d1) * t + 0.9375;
} else {
return n1 * (t -= 2.625 / d1) * t + 0.984375;
}
}
/**
* 跳跃缓入缓出
* @param t 时间参数 (0-1)
* @returns 缓动值
*/
static bounceInOut(t: number): number {
return t < 0.5
? (1 - Easing.bounceOut(1 - 2 * t)) / 2
: (1 + Easing.bounceOut(2 * t - 1)) / 2;
}
// 组合缓动
/**
* 创建自定义缓动函数(组合多个缓动)
* @param easingFunctions 缓动函数数组
* @param weights 权重数组,默认均等
* @returns 组合后的缓动函数
*/
static combine(
easingFunctions: ((t: number) => number)[],
weights?: number[]
): (t: number) => number {
if (!weights) {
weights = new Array(easingFunctions.length).fill(1 / easingFunctions.length);
}
return (t: number): number => {
let result = 0;
for (let i = 0; i < easingFunctions.length; i++) {
result += easingFunctions[i](t) * (weights![i] || 0);
}
return result;
};
}
/**
* 创建分段缓动函数
* @param segments 分段配置数组,每段包含 {duration, easing}
* @returns 分段缓动函数
*/
static piecewise(segments: Array<{duration: number; easing: (t: number) => number}>): (t: number) => number {
// 计算总持续时间
const totalDuration = segments.reduce((sum, seg) => sum + seg.duration, 0);
// 归一化持续时间
const normalizedSegments = segments.map((seg) => ({
...seg,
duration: seg.duration / totalDuration
}));
return (t: number): number => {
let accumulatedTime = 0;
for (const segment of normalizedSegments) {
if (t <= accumulatedTime + segment.duration) {
const localT = (t - accumulatedTime) / segment.duration;
return segment.easing(Math.max(0, Math.min(1, localT)));
}
accumulatedTime += segment.duration;
}
// 如果超出范围,返回最后一段的结束值
return normalizedSegments[normalizedSegments.length - 1].easing(1);
};
}
/**
* 创建反向缓动函数
* @param easing 原缓动函数
* @returns 反向缓动函数
*/
static reverse(easing: (t: number) => number): (t: number) => number {
return (t: number): number => 1 - easing(1 - t);
}
/**
* 创建镜像缓动函数(先正向再反向)
* @param easing 原缓动函数
* @returns 镜像缓动函数
*/
static mirror(easing: (t: number) => number): (t: number) => number {
return (t: number): number => {
if (t < 0.5) {
return easing(t * 2);
} else {
return easing(2 - t * 2);
}
};
}
// 常用预设
/** 平滑进入常用于UI动画 */
static readonly smoothIn = Easing.quadOut;
/** 平滑退出常用于UI动画 */
static readonly smoothOut = Easing.quadIn;
/** 快速进入(常用于出现动画) */
static readonly quickIn = Easing.cubicOut;
/** 快速退出(常用于消失动画) */
static readonly quickOut = Easing.cubicIn;
/** 自然运动(模拟物理) */
static readonly natural = Easing.quartOut;
/** 强调效果(吸引注意力) */
static readonly emphasize = Easing.backOut;
}

View File

@@ -0,0 +1,411 @@
import { Vector2 } from '../Vector2';
import { MathUtils } from '../MathUtils';
/**
* 插值器类型定义
*/
export type InterpolatorFunction<T> = (from: T, to: T, t: number) => T;
/**
* 关键帧数据结构
*/
export interface Keyframe<T> {
time: number;
value: T;
easing?: (t: number) => number;
}
/**
* 带缓存的插值器类
* 用于需要重复插值相同起始和目标值的情况
*/
export class CachedInterpolator<T> {
private from?: T;
private to?: T;
private interpolator: InterpolatorFunction<T>;
private cache: Map<number, T> = new Map();
constructor(interpolator: InterpolatorFunction<T>) {
this.interpolator = interpolator;
}
/**
* 设置插值范围
* @param from 起始值
* @param to 目标值
*/
setRange(from: T, to: T): void {
if (this.from !== from || this.to !== to) {
this.from = from;
this.to = to;
this.cache.clear();
}
}
/**
* 获取插值结果
* @param t 插值参数
* @returns 插值结果
*/
get(t: number): T {
if (!this.from || !this.to) {
throw new Error('插值范围未设置');
}
if (!this.cache.has(t)) {
const result = this.interpolator(this.from, this.to, t);
this.cache.set(t, result);
}
return this.cache.get(t)!;
}
/**
* 清空缓存
*/
clearCache(): void {
this.cache.clear();
}
}
/**
* 插值工具类
*
* 提供各种类型的插值功能,用于创建平滑的数值变化
*/
export class Interpolation {
// 基础插值
/**
* 数值线性插值
* @param from 起始值
* @param to 目标值
* @param t 插值参数 (0-1)
* @returns 插值结果
*/
static number(from: number, to: number, t: number): number {
return MathUtils.lerp(from, to, t);
}
/**
* 向量线性插值
* @param from 起始向量
* @param to 目标向量
* @param t 插值参数 (0-1)
* @returns 插值结果向量
*/
static vector2(from: Vector2, to: Vector2, t: number): Vector2 {
return Vector2.lerp(from, to, t);
}
/**
* 角度插值(处理角度环绕)
* @param from 起始角度(弧度)
* @param to 目标角度(弧度)
* @param t 插值参数 (0-1)
* @returns 插值结果角度
*/
static angle(from: number, to: number, t: number): number {
return MathUtils.lerpAngle(from, to, t);
}
/**
* 颜色插值RGB
* @param from 起始颜色 [r, g, b, a?]
* @param to 目标颜色 [r, g, b, a?]
* @param t 插值参数 (0-1)
* @returns 插值结果颜色
*/
static color(from: number[], to: number[], t: number): number[] {
const result: number[] = [];
const length = Math.max(from.length, to.length);
for (let i = 0; i < length; i++) {
const fromVal = from[i] ?? (i === 3 ? 1 : 0); // alpha默认为1
const toVal = to[i] ?? (i === 3 ? 1 : 0);
result[i] = MathUtils.lerp(fromVal, toVal, t);
}
return result;
}
// 高级插值
/**
* 三次样条插值
* @param p0 控制点0
* @param p1 控制点1起点
* @param p2 控制点2终点
* @param p3 控制点3
* @param t 插值参数 (0-1)
* @returns 插值结果
*/
static cubicSpline(p0: number, p1: number, p2: number, p3: number, t: number): number {
const t2 = t * t;
const t3 = t2 * t;
return 0.5 * (
(2 * p1) +
(-p0 + p2) * t +
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
(-p0 + 3 * p1 - 3 * p2 + p3) * t3
);
}
/**
* Hermite插值
* @param p0 起始点
* @param m0 起始切线
* @param p1 结束点
* @param m1 结束切线
* @param t 插值参数 (0-1)
* @returns 插值结果
*/
static hermite(p0: number, m0: number, p1: number, m1: number, t: number): number {
const t2 = t * t;
const t3 = t2 * t;
const h00 = 2 * t3 - 3 * t2 + 1;
const h10 = t3 - 2 * t2 + t;
const h01 = -2 * t3 + 3 * t2;
const h11 = t3 - t2;
return h00 * p0 + h10 * m0 + h01 * p1 + h11 * m1;
}
/**
* 球面线性插值(适用于方向向量)
* @param from 起始单位向量
* @param to 目标单位向量
* @param t 插值参数 (0-1)
* @returns 插值结果向量
*/
static slerp(from: Vector2, to: Vector2, t: number): Vector2 {
let dot = Vector2.dot(from, to);
// 如果点积为负,取反一个向量确保走最短路径
let toVec = to;
if (dot < 0) {
dot = -dot;
toVec = to.clone().negate();
}
// 如果向量几乎平行,使用线性插值
if (dot > 0.9995) {
return Vector2.lerp(from, toVec, t).normalize();
}
// 球面插值
const theta = Math.acos(Math.abs(dot));
const sinTheta = Math.sin(theta);
const a = Math.sin((1 - t) * theta) / sinTheta;
const b = Math.sin(t * theta) / sinTheta;
return new Vector2(
from.x * a + toVec.x * b,
from.y * a + toVec.y * b
);
}
// 缓存插值
/**
* 创建带缓存的插值器
* 用于需要重复插值相同起始和目标值的情况
* @param interpolator 插值函数
* @returns 缓存插值器实例
*/
static createCachedInterpolator<T>(interpolator: InterpolatorFunction<T>): CachedInterpolator<T> {
return new CachedInterpolator(interpolator);
}
// 多点插值
/**
* 样条曲线插值(通过多个控制点)
* @param points 控制点数组
* @param t 插值参数 (0-1)
* @returns 插值结果
*/
static spline(points: number[], t: number): number {
if (points.length === 0) return 0;
if (points.length === 1) return points[0];
if (points.length === 2) return MathUtils.lerp(points[0], points[1], t);
const n = points.length - 1;
const scaledT = t * n;
const segment = Math.floor(scaledT);
const localT = scaledT - segment;
const i = Math.max(0, Math.min(n - 1, segment));
const p0 = points[Math.max(0, i - 1)];
const p1 = points[i];
const p2 = points[Math.min(n, i + 1)];
const p3 = points[Math.min(n, i + 2)];
return Interpolation.cubicSpline(p0, p1, p2, p3, localT);
}
/**
* 向量样条曲线插值
* @param points 控制点数组
* @param t 插值参数 (0-1)
* @returns 插值结果向量
*/
static vectorSpline(points: Vector2[], t: number): Vector2 {
if (points.length === 0) return new Vector2();
if (points.length === 1) return points[0].clone();
if (points.length === 2) return Vector2.lerp(points[0], points[1], t);
const xPoints = points.map((p) => p.x);
const yPoints = points.map((p) => p.y);
return new Vector2(
Interpolation.spline(xPoints, t),
Interpolation.spline(yPoints, t)
);
}
// 时间轴插值
/**
* 关键帧动画插值
* @param keyframes 关键帧数组(按时间排序)
* @param time 当前时间
* @param interpolator 插值函数
* @returns 插值结果
*/
static keyframe<T>(
keyframes: Keyframe<T>[],
time: number,
interpolator: InterpolatorFunction<T>
): T {
if (keyframes.length === 0) {
throw new Error('至少需要一个关键帧');
}
if (keyframes.length === 1 || time <= keyframes[0].time) {
return keyframes[0].value;
}
if (time >= keyframes[keyframes.length - 1].time) {
return keyframes[keyframes.length - 1].value;
}
// 找到当前时间所在的区间
for (let i = 0; i < keyframes.length - 1; i++) {
const current = keyframes[i];
const next = keyframes[i + 1];
if (time >= current.time && time <= next.time) {
const duration = next.time - current.time;
const progress = duration > 0 ? (time - current.time) / duration : 0;
// 应用缓动函数
const easedProgress = current.easing ? current.easing(progress) : progress;
return interpolator(current.value, next.value, easedProgress);
}
}
return keyframes[keyframes.length - 1].value;
}
// 路径插值
/**
* 路径插值(沿着由点组成的路径)
* @param path 路径点数组
* @param t 插值参数 (0-1)
* @param closed 是否为闭合路径
* @returns 路径上的点
*/
static pathInterpolation(path: Vector2[], t: number, closed: boolean = false): Vector2 {
if (path.length === 0) return new Vector2();
if (path.length === 1) return path[0].clone();
const totalLength = Interpolation.getPathLength(path, closed);
const targetDistance = t * totalLength;
let accumulatedDistance = 0;
const segments = closed ? path.length : path.length - 1;
for (let i = 0; i < segments; i++) {
const start = path[i];
const end = path[(i + 1) % path.length];
const segmentLength = Vector2.distance(start, end);
if (accumulatedDistance + segmentLength >= targetDistance) {
const segmentT = (targetDistance - accumulatedDistance) / segmentLength;
return Vector2.lerp(start, end, segmentT);
}
accumulatedDistance += segmentLength;
}
return path[path.length - 1].clone();
}
/**
* 计算路径总长度
* @param path 路径点数组
* @param closed 是否为闭合路径
* @returns 路径总长度
*/
static getPathLength(path: Vector2[], closed: boolean = false): number {
if (path.length < 2) return 0;
let totalLength = 0;
const segments = closed ? path.length : path.length - 1;
for (let i = 0; i < segments; i++) {
const start = path[i];
const end = path[(i + 1) % path.length];
totalLength += Vector2.distance(start, end);
}
return totalLength;
}
// 实用工具
/**
* 创建数值插值器
* @param from 起始值
* @param to 目标值
* @returns 插值器函数
*/
static createNumberInterpolator(from: number, to: number): (t: number) => number {
return (t: number) => Interpolation.number(from, to, t);
}
/**
* 创建向量插值器
* @param from 起始向量
* @param to 目标向量
* @returns 插值器函数
*/
static createVectorInterpolator(from: Vector2, to: Vector2): (t: number) => Vector2 {
return (t: number) => Interpolation.vector2(from, to, t);
}
/**
* 创建组合插值器(插值多个值)
* @param interpolators 插值器数组
* @returns 组合插值器函数
*/
static createCompositeInterpolator<T>(
interpolators: InterpolatorFunction<T>[]
): (from: T[], to: T[], t: number) => T[] {
return (from: T[], to: T[], t: number): T[] => {
const result: T[] = [];
for (let i = 0; i < Math.min(interpolators.length, from.length, to.length); i++) {
result[i] = interpolators[i](from[i], to[i], t);
}
return result;
};
}
}

View File

@@ -0,0 +1,13 @@
/**
* 动画和插值模块
*
* 提供缓动函数和各种插值功能
*/
export { Easing } from './Easing';
export {
Interpolation,
CachedInterpolator,
type InterpolatorFunction,
type Keyframe
} from './Interpolation';

View File

@@ -0,0 +1,594 @@
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 };
}
}

View File

@@ -0,0 +1,445 @@
import { Vector2 } from '../Vector2';
import { Rectangle } from '../Rectangle';
import { Circle } from '../Circle';
/**
* 碰撞信息接口
*/
export interface CollisionInfo {
/** 是否发生碰撞 */
collided: boolean;
/** 碰撞法线(指向第二个对象) */
normal?: Vector2;
/** 穿透深度 */
penetration?: number;
/** 碰撞点 */
contactPoint?: Vector2;
}
/**
* 碰撞检测器
*
* 提供各种几何体之间的碰撞检测功能
*/
export class CollisionDetector {
// 点与几何体碰撞检测
/**
* 点与圆形碰撞检测
* @param point 点
* @param circle 圆形
* @returns 碰撞信息
*/
static pointCircle(point: Vector2, circle: Circle): CollisionInfo {
const distance = Vector2.distance(point, circle.center);
const collided = distance <= circle.radius;
if (!collided) {
return { collided: false };
}
const normal = distance > 0
? Vector2.subtract(point, circle.center).normalize()
: new Vector2(1, 0); // 默认法线
return {
collided: true,
normal,
penetration: circle.radius - distance,
contactPoint: point.clone()
};
}
/**
* 点与矩形碰撞检测
* @param point 点
* @param rect 矩形
* @returns 碰撞信息
*/
static pointRect(point: Vector2, rect: Rectangle): CollisionInfo {
const collided = rect.containsPoint(point);
if (!collided) {
return { collided: false };
}
// 计算到各边的距离
const distLeft = point.x - rect.left;
const distRight = rect.right - point.x;
const distTop = point.y - rect.top;
const distBottom = rect.bottom - point.y;
// 找到最小距离确定法线方向
const minDist = Math.min(distLeft, distRight, distTop, distBottom);
let normal: Vector2;
const penetration = minDist;
if (minDist === distLeft) {
normal = new Vector2(-1, 0);
} else if (minDist === distRight) {
normal = new Vector2(1, 0);
} else if (minDist === distTop) {
normal = new Vector2(0, -1);
} else {
normal = new Vector2(0, 1);
}
return {
collided: true,
normal,
penetration,
contactPoint: point.clone()
};
}
// 圆形碰撞检测
/**
* 圆形与圆形碰撞检测
* @param circle1 第一个圆形
* @param circle2 第二个圆形
* @returns 碰撞信息
*/
static circleCircle(circle1: Circle, circle2: Circle): CollisionInfo {
const distance = Vector2.distance(circle1.center, circle2.center);
const radiusSum = circle1.radius + circle2.radius;
const collided = distance <= radiusSum;
if (!collided) {
return { collided: false };
}
const normal = distance > 0
? Vector2.subtract(circle2.center, circle1.center).normalize()
: new Vector2(1, 0); // 默认法线
const penetration = radiusSum - distance;
const contactPoint = circle1.center.clone().add(
normal.clone().multiply(circle1.radius - penetration * 0.5)
);
return {
collided: true,
normal,
penetration,
contactPoint
};
}
/**
* 圆形与矩形碰撞检测
* @param circle 圆形
* @param rect 矩形
* @returns 碰撞信息
*/
static circleRect(circle: Circle, rect: Rectangle): CollisionInfo {
// 找到矩形上离圆心最近的点
const closestPoint = rect.closestPointTo(circle.center);
// 检查是否碰撞
const distance = Vector2.distance(circle.center, closestPoint);
const collided = distance <= circle.radius;
if (!collided) {
return { collided: false };
}
// 计算法线和穿透深度
const normal = distance > 0
? Vector2.subtract(closestPoint, circle.center).normalize()
: new Vector2(0, -1); // 默认法线(圆心在矩形内部时)
const penetration = circle.radius - distance;
return {
collided: true,
normal,
penetration,
contactPoint: closestPoint
};
}
// 矩形碰撞检测
/**
* 矩形与矩形碰撞检测AABB
* @param rect1 第一个矩形
* @param rect2 第二个矩形
* @returns 碰撞信息
*/
static rectRect(rect1: Rectangle, rect2: Rectangle): CollisionInfo {
const collided = rect1.intersects(rect2);
if (!collided) {
return { collided: false };
}
// 计算重叠区域
const overlapLeft = Math.max(rect1.left, rect2.left);
const overlapRight = Math.min(rect1.right, rect2.right);
const overlapTop = Math.max(rect1.top, rect2.top);
const overlapBottom = Math.min(rect1.bottom, rect2.bottom);
const overlapWidth = overlapRight - overlapLeft;
const overlapHeight = overlapBottom - overlapTop;
// 确定分离方向(最小重叠轴)
let normal: Vector2;
let penetration: number;
if (overlapWidth < overlapHeight) {
// 水平分离
penetration = overlapWidth;
if (rect1.centerX < rect2.centerX) {
normal = new Vector2(-1, 0);
} else {
normal = new Vector2(1, 0);
}
} else {
// 垂直分离
penetration = overlapHeight;
if (rect1.centerY < rect2.centerY) {
normal = new Vector2(0, -1);
} else {
normal = new Vector2(0, 1);
}
}
const contactPoint = new Vector2(
(overlapLeft + overlapRight) * 0.5,
(overlapTop + overlapBottom) * 0.5
);
return {
collided: true,
normal,
penetration,
contactPoint
};
}
// 射线投射
/**
* 射线与圆形相交检测
* @param rayOrigin 射线起点
* @param rayDirection 射线方向(单位向量)
* @param circle 圆形
* @param maxDistance 最大检测距离,默认无限
* @returns 碰撞信息,包含距离信息
*/
static rayCircle(
rayOrigin: Vector2,
rayDirection: Vector2,
circle: Circle,
maxDistance: number = Infinity
): CollisionInfo & { distance?: number } {
const oc = Vector2.subtract(rayOrigin, circle.center);
const a = rayDirection.lengthSquared;
const b = 2 * Vector2.dot(oc, rayDirection);
const c = oc.lengthSquared - circle.radius * circle.radius;
const discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
return { collided: false };
}
const sqrt = Math.sqrt(discriminant);
const t1 = (-b - sqrt) / (2 * a);
const t2 = (-b + sqrt) / (2 * a);
// 选择最近的正距离
const t = t1 >= 0 ? t1 : t2;
if (t < 0 || t > maxDistance) {
return { collided: false };
}
const contactPoint = rayOrigin.clone().add(rayDirection.clone().multiply(t));
const normal = Vector2.subtract(contactPoint, circle.center).normalize();
return {
collided: true,
normal,
contactPoint,
distance: t,
penetration: 0 // 射线检测不计算穿透
};
}
/**
* 射线与矩形相交检测
* @param rayOrigin 射线起点
* @param rayDirection 射线方向(单位向量)
* @param rect 矩形
* @param maxDistance 最大检测距离,默认无限
* @returns 碰撞信息,包含距离信息
*/
static rayRect(
rayOrigin: Vector2,
rayDirection: Vector2,
rect: Rectangle,
maxDistance: number = Infinity
): CollisionInfo & { distance?: number } {
// 避免除零
const invDirX = rayDirection.x !== 0 ? 1 / rayDirection.x : 1e10;
const invDirY = rayDirection.y !== 0 ? 1 / rayDirection.y : 1e10;
// 计算与各边的交点参数
const t1 = (rect.left - rayOrigin.x) * invDirX;
const t2 = (rect.right - rayOrigin.x) * invDirX;
const t3 = (rect.top - rayOrigin.y) * invDirY;
const t4 = (rect.bottom - rayOrigin.y) * invDirY;
const tmin = Math.max(Math.min(t1, t2), Math.min(t3, t4));
const tmax = Math.min(Math.max(t1, t2), Math.max(t3, t4));
// 没有交点或交点在射线反方向
if (tmax < 0 || tmin > tmax || tmin > maxDistance) {
return { collided: false };
}
const t = tmin >= 0 ? tmin : tmax;
const contactPoint = rayOrigin.clone().add(rayDirection.clone().multiply(t));
// 确定法线方向
let normal: Vector2;
const epsilon = 1e-6;
if (Math.abs(contactPoint.x - rect.left) < epsilon) {
normal = new Vector2(-1, 0);
} else if (Math.abs(contactPoint.x - rect.right) < epsilon) {
normal = new Vector2(1, 0);
} else if (Math.abs(contactPoint.y - rect.top) < epsilon) {
normal = new Vector2(0, -1);
} else {
normal = new Vector2(0, 1);
}
return {
collided: true,
normal,
contactPoint,
distance: t,
penetration: 0 // 射线检测不计算穿透
};
}
// 线段相交检测
/**
* 线段与线段相交检测
* @param p1 第一条线段起点
* @param p2 第一条线段终点
* @param p3 第二条线段起点
* @param p4 第二条线段终点
* @returns 碰撞信息
*/
static lineSegmentLineSegment(p1: Vector2, p2: Vector2, p3: Vector2, p4: Vector2): CollisionInfo {
const d1 = Vector2.subtract(p2, p1);
const d2 = Vector2.subtract(p4, p3);
const d3 = Vector2.subtract(p3, p1);
const cross = Vector2.cross(d1, d2);
if (Math.abs(cross) < Number.EPSILON) {
// 平行或共线
return { collided: false };
}
const t1 = Vector2.cross(d3, d2) / cross;
const t2 = Vector2.cross(d3, d1) / cross;
if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
const contactPoint = p1.clone().add(d1.clone().multiply(t1));
// 计算法线(垂直于第一条线段)
const normal = d1.perpendicular().normalize();
return {
collided: true,
normal,
contactPoint,
penetration: 0 // 线段相交不计算穿透
};
}
return { collided: false };
}
/**
* 线段与圆形相交检测
* @param lineStart 线段起点
* @param lineEnd 线段终点
* @param circle 圆形
* @returns 碰撞信息
*/
static lineSegmentCircle(lineStart: Vector2, lineEnd: Vector2, circle: Circle): CollisionInfo {
const d = Vector2.subtract(lineEnd, lineStart);
const f = Vector2.subtract(lineStart, circle.center);
const a = Vector2.dot(d, d);
const b = 2 * Vector2.dot(f, d);
const c = Vector2.dot(f, f) - circle.radius * circle.radius;
const discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
return { collided: false };
}
const sqrt = Math.sqrt(discriminant);
const t1 = (-b - sqrt) / (2 * a);
const t2 = (-b + sqrt) / (2 * a);
// 检查交点是否在线段上
const validT = [];
if (t1 >= 0 && t1 <= 1) validT.push(t1);
if (t2 >= 0 && t2 <= 1) validT.push(t2);
if (validT.length === 0) {
return { collided: false };
}
// 使用最近的交点
const t = validT[0];
const contactPoint = lineStart.clone().add(d.clone().multiply(t));
const normal = Vector2.subtract(contactPoint, circle.center).normalize();
return {
collided: true,
normal,
contactPoint,
penetration: 0 // 线段相交不计算穿透
};
}
// 快速排斥测试
/**
* AABB包围盒快速排斥测试
* @param bounds1 第一个包围盒
* @param bounds2 第二个包围盒
* @returns 是否可能相交
*/
static aabbTest(bounds1: Rectangle, bounds2: Rectangle): boolean {
return bounds1.intersects(bounds2);
}
/**
* 圆形包围盒快速排斥测试
* @param center1 第一个圆心
* @param radius1 第一个半径
* @param center2 第二个圆心
* @param radius2 第二个半径
* @returns 是否可能相交
*/
static circleTest(center1: Vector2, radius1: number, center2: Vector2, radius2: number): boolean {
const distance = Vector2.distance(center1, center2);
return distance <= radius1 + radius2;
}
}

View File

@@ -0,0 +1,7 @@
/**
* 碰撞检测模块
*
* 提供各种几何体间的碰撞检测功能
*/
export { CollisionDetector, type CollisionInfo } from './CollisionDetector';

View File

@@ -0,0 +1,556 @@
/**
* Color utility class for game engine
* 游戏引擎颜色工具类
*
* Provides color conversion, manipulation, and packing utilities.
* 提供颜色转换、操作和打包工具。
*/
/**
* RGBA color components
* RGBA 颜色分量
*/
export interface RGBA {
r: number;
g: number;
b: number;
a: number;
}
/**
* HSL color components
* HSL 颜色分量
*/
export interface HSL {
h: number;
s: number;
l: number;
}
/**
* Color class for color manipulation and conversion
* 颜色类,用于颜色操作和转换
*/
export class Color {
/** Red component (0-255) | 红色分量 (0-255) */
public r: number;
/** Green component (0-255) | 绿色分量 (0-255) */
public g: number;
/** Blue component (0-255) | 蓝色分量 (0-255) */
public b: number;
/** Alpha component (0-1) | 透明度分量 (0-1) */
public a: number;
// ===== Predefined Colors | 预定义颜色 =====
/** White (0xFFFFFF) | 白色 */
static readonly WHITE = new Color(255, 255, 255);
/** Black (0x000000) | 黑色 */
static readonly BLACK = new Color(0, 0, 0);
/** Red (0xFF0000) | 红色 */
static readonly RED = new Color(255, 0, 0);
/** Green (0x00FF00) | 绿色 */
static readonly GREEN = new Color(0, 255, 0);
/** Blue (0x0000FF) | 蓝色 */
static readonly BLUE = new Color(0, 0, 255);
/** Yellow (0xFFFF00) | 黄色 */
static readonly YELLOW = new Color(255, 255, 0);
/** Cyan (0x00FFFF) | 青色 */
static readonly CYAN = new Color(0, 255, 255);
/** Magenta (0xFF00FF) | 品红色 */
static readonly MAGENTA = new Color(255, 0, 255);
/** Transparent (0x00000000) | 透明 */
static readonly TRANSPARENT = new Color(0, 0, 0, 0);
/** Gray (0x808080) | 灰色 */
static readonly GRAY = new Color(128, 128, 128);
/**
* Create a new Color instance
* 创建新的 Color 实例
*/
constructor(r: number = 255, g: number = 255, b: number = 255, a: number = 1) {
this.r = Math.round(Math.max(0, Math.min(255, r)));
this.g = Math.round(Math.max(0, Math.min(255, g)));
this.b = Math.round(Math.max(0, Math.min(255, b)));
this.a = Math.max(0, Math.min(1, a));
}
// ===== Factory Methods | 工厂方法 =====
/**
* Create color from hex string
* 从十六进制字符串创建颜色
* @param hex Hex string (e.g., "#FF0000", "#F00", "FF0000") | 十六进制字符串
* @param alpha Optional alpha value (0-1) | 可选的透明度值
*/
static fromHex(hex: string, alpha: number = 1): Color {
const { r, g, b } = Color.hexToRgb(hex);
return new Color(r, g, b, alpha);
}
/**
* Create color from packed uint32 (0xRRGGBB or 0xAARRGGBB)
* 从打包的 uint32 创建颜色
* @param value Packed color value | 打包的颜色值
* @param hasAlpha Whether value includes alpha | 是否包含透明度
*/
static fromUint32(value: number, hasAlpha: boolean = false): Color {
if (hasAlpha) {
const a = ((value >> 24) & 0xFF) / 255;
const r = (value >> 16) & 0xFF;
const g = (value >> 8) & 0xFF;
const b = value & 0xFF;
return new Color(r, g, b, a);
} else {
const r = (value >> 16) & 0xFF;
const g = (value >> 8) & 0xFF;
const b = value & 0xFF;
return new Color(r, g, b);
}
}
/**
* Create color from HSL values
* 从 HSL 值创建颜色
* @param h Hue (0-360) | 色相
* @param s Saturation (0-1) | 饱和度
* @param l Lightness (0-1) | 亮度
* @param a Alpha (0-1) | 透明度
*/
static fromHSL(h: number, s: number, l: number, a: number = 1): Color {
const { r, g, b } = Color.hslToRgb(h, s, l);
return new Color(r, g, b, a);
}
/**
* Create color from normalized float values (0-1)
* 从归一化浮点值创建颜色 (0-1)
*/
static fromFloat(r: number, g: number, b: number, a: number = 1): Color {
return new Color(r * 255, g * 255, b * 255, a);
}
// ===== Conversion Methods | 转换方法 =====
/**
* Convert hex string to RGB
* 将十六进制字符串转换为 RGB
*/
static hexToRgb(hex: string): { r: number; g: number; b: number } {
const colorHex = hex.replace('#', '');
let r = 255, g = 255, b = 255;
if (colorHex.length === 6) {
r = parseInt(colorHex.substring(0, 2), 16);
g = parseInt(colorHex.substring(2, 4), 16);
b = parseInt(colorHex.substring(4, 6), 16);
} else if (colorHex.length === 3) {
r = parseInt(colorHex[0] + colorHex[0], 16);
g = parseInt(colorHex[1] + colorHex[1], 16);
b = parseInt(colorHex[2] + colorHex[2], 16);
} else if (colorHex.length === 8) {
// AARRGGBB format
r = parseInt(colorHex.substring(2, 4), 16);
g = parseInt(colorHex.substring(4, 6), 16);
b = parseInt(colorHex.substring(6, 8), 16);
}
return { r, g, b };
}
/**
* Convert RGB to hex string
* 将 RGB 转换为十六进制字符串
*/
static rgbToHex(r: number, g: number, b: number): string {
const toHex = (n: number) => Math.round(n).toString(16).padStart(2, '0');
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
/**
* Convert HSL to RGB
* 将 HSL 转换为 RGB
*/
static hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
h = ((h % 360) + 360) % 360 / 360;
s = Math.max(0, Math.min(1, s));
l = Math.max(0, Math.min(1, l));
let r: number, g: number, b: number;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p: number, q: number, t: number) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
}
/**
* Convert RGB to HSL
* 将 RGB 转换为 HSL
*/
static rgbToHsl(r: number, g: number, b: number): HSL {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
let s = 0;
const l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
break;
case g:
h = ((b - r) / d + 2) / 6;
break;
case b:
h = ((r - g) / d + 4) / 6;
break;
}
}
return { h: h * 360, s, l };
}
// ===== Packing Methods | 打包方法 =====
/**
* Pack color to uint32 (0xRRGGBB)
* 打包颜色为 uint32 (0xRRGGBB)
*/
static packRGB(r: number, g: number, b: number): number {
return ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
/**
* Pack color to uint32 (0xAARRGGBB)
* 打包颜色为 uint32 (0xAARRGGBB)
*/
static packARGB(r: number, g: number, b: number, a: number): number {
const alpha = Math.round(a * 255);
return ((alpha & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
/**
* Pack color to uint32 for WebGL (0xAABBGGRR)
* 打包颜色为 WebGL 格式的 uint32 (0xAABBGGRR)
*/
static packABGR(r: number, g: number, b: number, a: number): number {
const alpha = Math.round(a * 255);
return ((alpha & 0xFF) << 24) | ((b & 0xFF) << 16) | ((g & 0xFF) << 8) | (r & 0xFF);
}
/**
* Pack hex string and alpha to WebGL uint32 (0xAABBGGRR)
* 将十六进制字符串和透明度打包为 WebGL 格式 uint32
*/
static packHexAlpha(hex: string, alpha: number): number {
const { r, g, b } = Color.hexToRgb(hex);
return Color.packABGR(r, g, b, alpha);
}
/**
* Unpack uint32 to RGBA (assumes 0xAARRGGBB)
* 解包 uint32 为 RGBA (假设格式为 0xAARRGGBB)
*/
static unpackARGB(value: number): RGBA {
return {
a: ((value >> 24) & 0xFF) / 255,
r: (value >> 16) & 0xFF,
g: (value >> 8) & 0xFF,
b: value & 0xFF
};
}
/**
* Unpack uint32 to RGBA (assumes 0xAABBGGRR - WebGL format)
* 解包 uint32 为 RGBA (假设格式为 0xAABBGGRR - WebGL 格式)
*/
static unpackABGR(value: number): RGBA {
return {
a: ((value >> 24) & 0xFF) / 255,
b: (value >> 16) & 0xFF,
g: (value >> 8) & 0xFF,
r: value & 0xFF
};
}
// ===== Color Operations | 颜色操作 =====
/**
* Interpolate between two colors
* 在两个颜色之间插值
*/
static lerp(from: Color, to: Color, t: number): Color {
t = Math.max(0, Math.min(1, t));
return new Color(
from.r + (to.r - from.r) * t,
from.g + (to.g - from.g) * t,
from.b + (to.b - from.b) * t,
from.a + (to.a - from.a) * t
);
}
/**
* Interpolate between two packed uint32 colors (0xRRGGBB)
* 在两个打包的 uint32 颜色之间插值
*/
static lerpUint32(from: number, to: number, t: number): number {
t = Math.max(0, Math.min(1, t));
const fromR = (from >> 16) & 0xFF;
const fromG = (from >> 8) & 0xFF;
const fromB = from & 0xFF;
const toR = (to >> 16) & 0xFF;
const toG = (to >> 8) & 0xFF;
const toB = to & 0xFF;
const r = Math.round(fromR + (toR - fromR) * t);
const g = Math.round(fromG + (toG - fromG) * t);
const b = Math.round(fromB + (toB - fromB) * t);
return (r << 16) | (g << 8) | b;
}
/**
* Mix two colors
* 混合两个颜色
*/
static mix(color1: Color, color2: Color, ratio: number = 0.5): Color {
return Color.lerp(color1, color2, ratio);
}
/**
* Lighten a color
* 使颜色变亮
*/
static lighten(color: Color, amount: number): Color {
const hsl = Color.rgbToHsl(color.r, color.g, color.b);
hsl.l = Math.min(1, hsl.l + amount);
const rgb = Color.hslToRgb(hsl.h, hsl.s, hsl.l);
return new Color(rgb.r, rgb.g, rgb.b, color.a);
}
/**
* Darken a color
* 使颜色变暗
*/
static darken(color: Color, amount: number): Color {
const hsl = Color.rgbToHsl(color.r, color.g, color.b);
hsl.l = Math.max(0, hsl.l - amount);
const rgb = Color.hslToRgb(hsl.h, hsl.s, hsl.l);
return new Color(rgb.r, rgb.g, rgb.b, color.a);
}
/**
* Saturate a color
* 增加颜色饱和度
*/
static saturate(color: Color, amount: number): Color {
const hsl = Color.rgbToHsl(color.r, color.g, color.b);
hsl.s = Math.min(1, hsl.s + amount);
const rgb = Color.hslToRgb(hsl.h, hsl.s, hsl.l);
return new Color(rgb.r, rgb.g, rgb.b, color.a);
}
/**
* Desaturate a color
* 降低颜色饱和度
*/
static desaturate(color: Color, amount: number): Color {
const hsl = Color.rgbToHsl(color.r, color.g, color.b);
hsl.s = Math.max(0, hsl.s - amount);
const rgb = Color.hslToRgb(hsl.h, hsl.s, hsl.l);
return new Color(rgb.r, rgb.g, rgb.b, color.a);
}
/**
* Invert a color
* 反转颜色
*/
static invert(color: Color): Color {
return new Color(255 - color.r, 255 - color.g, 255 - color.b, color.a);
}
/**
* Convert color to grayscale
* 将颜色转换为灰度
*/
static grayscale(color: Color): Color {
const gray = Math.round(0.299 * color.r + 0.587 * color.g + 0.114 * color.b);
return new Color(gray, gray, gray, color.a);
}
/**
* Get color luminance (perceived brightness)
* 获取颜色亮度(感知亮度)
*/
static luminance(color: Color): number {
return (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255;
}
/**
* Get contrast ratio between two colors
* 获取两个颜色之间的对比度
*/
static contrastRatio(color1: Color, color2: Color): number {
const l1 = Color.luminance(color1);
const l2 = Color.luminance(color2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
// ===== Instance Methods | 实例方法 =====
/**
* Convert to hex string
* 转换为十六进制字符串
*/
toHex(): string {
return Color.rgbToHex(this.r, this.g, this.b);
}
/**
* Convert to hex string with alpha
* 转换为带透明度的十六进制字符串
*/
toHexAlpha(): string {
const alphaHex = Math.round(this.a * 255).toString(16).padStart(2, '0');
return `#${alphaHex}${this.toHex().slice(1)}`;
}
/**
* Convert to CSS rgba string
* 转换为 CSS rgba 字符串
*/
toRgba(): string {
return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`;
}
/**
* Convert to CSS rgb string
* 转换为 CSS rgb 字符串
*/
toRgb(): string {
return `rgb(${this.r}, ${this.g}, ${this.b})`;
}
/**
* Convert to HSL
* 转换为 HSL
*/
toHSL(): HSL {
return Color.rgbToHsl(this.r, this.g, this.b);
}
/**
* Pack to uint32 (0xRRGGBB)
* 打包为 uint32 (0xRRGGBB)
*/
toUint32(): number {
return Color.packRGB(this.r, this.g, this.b);
}
/**
* Pack to uint32 with alpha (0xAARRGGBB)
* 打包为带透明度的 uint32 (0xAARRGGBB)
*/
toUint32Alpha(): number {
return Color.packARGB(this.r, this.g, this.b, this.a);
}
/**
* Pack to WebGL uint32 (0xAABBGGRR)
* 打包为 WebGL 格式 uint32 (0xAABBGGRR)
*/
toWebGL(): number {
return Color.packABGR(this.r, this.g, this.b, this.a);
}
/**
* Get normalized float array [r, g, b, a] (0-1)
* 获取归一化浮点数组 [r, g, b, a] (0-1)
*/
toFloatArray(): [number, number, number, number] {
return [this.r / 255, this.g / 255, this.b / 255, this.a];
}
/**
* Clone this color
* 克隆此颜色
*/
clone(): Color {
return new Color(this.r, this.g, this.b, this.a);
}
/**
* Set color values
* 设置颜色值
*/
set(r: number, g: number, b: number, a?: number): this {
this.r = Math.round(Math.max(0, Math.min(255, r)));
this.g = Math.round(Math.max(0, Math.min(255, g)));
this.b = Math.round(Math.max(0, Math.min(255, b)));
if (a !== undefined) {
this.a = Math.max(0, Math.min(1, a));
}
return this;
}
/**
* Copy from another color
* 从另一个颜色复制
*/
copy(other: Color): this {
this.r = other.r;
this.g = other.g;
this.b = other.b;
this.a = other.a;
return this;
}
/**
* Check equality with another color
* 检查与另一个颜色是否相等
*/
equals(other: Color): boolean {
return this.r === other.r && this.g === other.g && this.b === other.b && this.a === other.a;
}
/**
* String representation
* 字符串表示
*/
toString(): string {
return `Color(${this.r}, ${this.g}, ${this.b}, ${this.a})`;
}
}

View File

@@ -0,0 +1,567 @@
import { Vector2 } from './Vector2';
/**
* 数学工具函数集合
*
* 提供常用的数学运算、插值、随机数生成等实用工具函数
*/
export class MathUtils {
// 数学常量
/** 圆周率 */
static readonly PI = Math.PI;
/** 2π */
static readonly TWO_PI = Math.PI * 2;
/** π/2 */
static readonly HALF_PI = Math.PI * 0.5;
/** π/4 */
static readonly QUARTER_PI = Math.PI * 0.25;
/** 角度到弧度转换系数 */
static readonly DEG_TO_RAD = Math.PI / 180;
/** 弧度到角度转换系数 */
static readonly RAD_TO_DEG = 180 / Math.PI;
/** 黄金比例 */
static readonly GOLDEN_RATIO = (1 + Math.sqrt(5)) * 0.5;
/** 默认浮点数比较容差 */
static readonly EPSILON = Number.EPSILON;
// 角度转换
/**
* 角度转弧度
* @param degrees 角度值
* @returns 弧度值
*/
static degToRad(degrees: number): number {
return degrees * MathUtils.DEG_TO_RAD;
}
/**
* 弧度转角度
* @param radians 弧度值
* @returns 角度值
*/
static radToDeg(radians: number): number {
return radians * MathUtils.RAD_TO_DEG;
}
/**
* 规范化角度到[0, 2π)范围
* @param radians 角度(弧度)
* @returns 规范化后的角度
*/
static normalizeAngle(radians: number): number {
while (radians < 0) radians += MathUtils.TWO_PI;
while (radians >= MathUtils.TWO_PI) radians -= MathUtils.TWO_PI;
return radians;
}
/**
* 规范化角度到(-π, π]范围
* @param radians 角度(弧度)
* @returns 规范化后的角度
*/
static normalizeAngleSigned(radians: number): number {
while (radians <= -Math.PI) radians += MathUtils.TWO_PI;
while (radians > Math.PI) radians -= MathUtils.TWO_PI;
return radians;
}
/**
* 计算两个角度之间的最短角度差
* @param from 起始角度(弧度)
* @param to 目标角度(弧度)
* @returns 角度差(-π到π)
*/
static angleDifference(from: number, to: number): number {
let diff = to - from;
diff = MathUtils.normalizeAngleSigned(diff);
return diff;
}
/**
* 角度插值(处理角度环绕)
* @param from 起始角度(弧度)
* @param to 目标角度(弧度)
* @param t 插值参数0到1
* @returns 插值结果角度
*/
static lerpAngle(from: number, to: number, t: number): number {
const diff = MathUtils.angleDifference(from, to);
return from + diff * t;
}
// 数值操作
/**
* 限制数值在指定范围内
* @param value 待限制的值
* @param min 最小值
* @param max 最大值
* @returns 限制后的值
*/
static clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
/**
* 限制数值在0到1之间
* @param value 待限制的值
* @returns 限制后的值
*/
static clamp01(value: number): number {
return Math.max(0, Math.min(1, value));
}
/**
* 线性插值
* @param a 起始值
* @param b 目标值
* @param t 插值参数0到1
* @returns 插值结果
*/
static lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}
/**
* 反向线性插值(获取插值参数)
* @param a 起始值
* @param b 目标值
* @param value 当前值
* @returns 插值参数
*/
static inverseLerp(a: number, b: number, value: number): number {
if (Math.abs(b - a) < MathUtils.EPSILON) {
return 0;
}
return (value - a) / (b - a);
}
/**
* 重映射数值从一个范围到另一个范围
* @param value 输入值
* @param inMin 输入范围最小值
* @param inMax 输入范围最大值
* @param outMin 输出范围最小值
* @param outMax 输出范围最大值
* @returns 重映射后的值
*/
static remap(value: number, inMin: number, inMax: number, outMin: number, outMax: number): number {
const t = MathUtils.inverseLerp(inMin, inMax, value);
return MathUtils.lerp(outMin, outMax, t);
}
/**
* 平滑阶跃函数Hermite插值
* @param t 输入参数0到1
* @returns 平滑输出0到1
*/
static smoothStep(t: number): number {
t = MathUtils.clamp01(t);
return t * t * (3 - 2 * t);
}
/**
* 更平滑的阶跃函数
* @param t 输入参数0到1
* @returns 平滑输出0到1
*/
static smootherStep(t: number): number {
t = MathUtils.clamp01(t);
return t * t * t * (t * (t * 6 - 15) + 10);
}
// 比较操作
/**
* 浮点数相等比较
* @param a 数值a
* @param b 数值b
* @param epsilon 容差默认为EPSILON
* @returns 是否相等
*/
static approximately(a: number, b: number, epsilon: number = MathUtils.EPSILON): boolean {
return Math.abs(a - b) < epsilon;
}
/**
* 检查数值是否为零
* @param value 数值
* @param epsilon 容差默认为EPSILON
* @returns 是否为零
*/
static isZero(value: number, epsilon: number = MathUtils.EPSILON): boolean {
return Math.abs(value) < epsilon;
}
/**
* 获取数值的符号
* @param value 数值
* @returns 1、-1或0
*/
static sign(value: number): number {
return value > 0 ? 1 : value < 0 ? -1 : 0;
}
// 随机数生成
/**
* 生成指定范围内的随机数
* @param min 最小值(包含)
* @param max 最大值(不包含)
* @returns 随机数
*/
static random(min: number = 0, max: number = 1): number {
return Math.random() * (max - min) + min;
}
/**
* 生成指定范围内的随机整数
* @param min 最小值(包含)
* @param max 最大值(包含)
* @returns 随机整数
*/
static randomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* 随机选择数组中的一个元素
* @param array 数组
* @returns 随机元素
*/
static randomChoice<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
/**
* 生成随机布尔值
* @param probability 为true的概率0到1默认0.5
* @returns 随机布尔值
*/
static randomBoolean(probability: number = 0.5): boolean {
return Math.random() < probability;
}
/**
* 生成单位圆内的随机点
* @returns 随机向量
*/
static randomInUnitCircle(): Vector2 {
const angle = Math.random() * MathUtils.TWO_PI;
const radius = Math.sqrt(Math.random());
return Vector2.fromPolar(radius, angle);
}
/**
* 生成单位圆上的随机点
* @returns 随机单位向量
*/
static randomOnUnitCircle(): Vector2 {
const angle = Math.random() * MathUtils.TWO_PI;
return Vector2.fromAngle(angle);
}
// 数学函数
/**
* 快速平方根倒数(用于归一化)
* @param value 输入值
* @returns 平方根倒数
*/
static fastInverseSqrt(value: number): number {
// 简化版本现代JavaScript引擎优化很好
return 1 / Math.sqrt(value);
}
/**
* 快速幂运算(整数指数)
* @param base 底数
* @param exponent 指数(整数)
* @returns 幂运算结果
*/
static fastPow(base: number, exponent: number): number {
if (exponent === 0) return 1;
if (exponent === 1) return base;
if (exponent === 2) return base * base;
if (exponent === 3) return base * base * base;
return Math.pow(base, exponent);
}
/**
* 阶乘
* @param n 非负整数
* @returns 阶乘结果
*/
static factorial(n: number): number {
if (n < 0) return NaN;
if (n === 0 || n === 1) return 1;
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
}
/**
* 最大公约数
* @param a 整数a
* @param b 整数b
* @returns 最大公约数
*/
static gcd(a: number, b: number): number {
a = Math.abs(Math.floor(a));
b = Math.abs(Math.floor(b));
while (b !== 0) {
const temp = b;
b = a % b;
a = temp;
}
return a;
}
/**
* 最小公倍数
* @param a 整数a
* @param b 整数b
* @returns 最小公倍数
*/
static lcm(a: number, b: number): number {
return Math.abs(a * b) / MathUtils.gcd(a, b);
}
// 序列和级数
/**
* 斐波那契数列
* @param n 项数
* @returns 第n项斐波那契数
*/
static fibonacci(n: number): number {
if (n <= 0) return 0;
if (n === 1) return 1;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
const temp = a + b;
a = b;
b = temp;
}
return b;
}
/**
* 等差数列求和
* @param first 首项
* @param last 末项
* @param count 项数
* @returns 等差数列和
*/
static arithmeticSum(first: number, last: number, count: number): number {
return (first + last) * count * 0.5;
}
/**
* 等比数列求和
* @param first 首项
* @param ratio 公比
* @param count 项数
* @returns 等比数列和
*/
static geometricSum(first: number, ratio: number, count: number): number {
if (Math.abs(ratio - 1) < MathUtils.EPSILON) {
return first * count;
}
return first * (1 - Math.pow(ratio, count)) / (1 - ratio);
}
// 曲线和插值
/**
* 贝塞尔二次曲线
* @param p0 控制点0
* @param p1 控制点1
* @param p2 控制点2
* @param t 参数0到1
* @returns 曲线上的点
*/
static quadraticBezier(p0: Vector2, p1: Vector2, p2: Vector2, t: number): Vector2 {
const u = 1 - t;
const tt = t * t;
const uu = u * u;
return new Vector2(
uu * p0.x + 2 * u * t * p1.x + tt * p2.x,
uu * p0.y + 2 * u * t * p1.y + tt * p2.y
);
}
/**
* 贝塞尔三次曲线
* @param p0 控制点0
* @param p1 控制点1
* @param p2 控制点2
* @param p3 控制点3
* @param t 参数0到1
* @returns 曲线上的点
*/
static cubicBezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: number): Vector2 {
const u = 1 - t;
const tt = t * t;
const uu = u * u;
const uuu = uu * u;
const ttt = tt * t;
return new Vector2(
uuu * p0.x + 3 * uu * t * p1.x + 3 * u * tt * p2.x + ttt * p3.x,
uuu * p0.y + 3 * uu * t * p1.y + 3 * u * tt * p2.y + ttt * p3.y
);
}
/**
* Catmull-Rom样条插值
* @param p0 控制点0
* @param p1 控制点1
* @param p2 控制点2
* @param p3 控制点3
* @param t 参数0到1
* @returns 插值结果点
*/
static catmullRom(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: number): Vector2 {
const t2 = t * t;
const t3 = t2 * t;
const x = 0.5 * (
(2 * p1.x) +
(-p0.x + p2.x) * t +
(2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
(-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3
);
const y = 0.5 * (
(2 * p1.y) +
(-p0.y + p2.y) * t +
(2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
(-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3
);
return new Vector2(x, y);
}
// 噪声函数
/**
* 简单伪随机噪声(基于种子)
* @param x 输入X
* @param y 输入Y
* @param seed 种子
* @returns 噪声值0到1
*/
static noise(x: number, y: number = 0, seed: number = 0): number {
const n = Math.sin(x * 12.9898 + y * 78.233 + seed * 37.719) * 43758.5453;
return n - Math.floor(n);
}
/**
* 平滑噪声
* @param x 输入X
* @param y 输入Y
* @param seed 种子
* @returns 平滑噪声值0到1
*/
static smoothNoise(x: number, y: number = 0, seed: number = 0): number {
const intX = Math.floor(x);
const intY = Math.floor(y);
const fracX = x - intX;
const fracY = y - intY;
const a = MathUtils.noise(intX, intY, seed);
const b = MathUtils.noise(intX + 1, intY, seed);
const c = MathUtils.noise(intX, intY + 1, seed);
const d = MathUtils.noise(intX + 1, intY + 1, seed);
const i1 = MathUtils.lerp(a, b, fracX);
const i2 = MathUtils.lerp(c, d, fracX);
return MathUtils.lerp(i1, i2, fracY);
}
// 实用工具
/**
* 将数值转换为指定精度
* @param value 数值
* @param precision 精度(小数位数)
* @returns 转换后的数值
*/
static toPrecision(value: number, precision: number): number {
const factor = Math.pow(10, precision);
return Math.round(value * factor) / factor;
}
/**
* 检查数值是否在指定范围内
* @param value 数值
* @param min 最小值
* @param max 最大值
* @returns 是否在范围内
*/
static inRange(value: number, min: number, max: number): boolean {
return value >= min && value <= max;
}
/**
* 获取数组中的最小值
* @param values 数值数组
* @returns 最小值
*/
static min(...values: number[]): number {
return Math.min(...values);
}
/**
* 获取数组中的最大值
* @param values 数值数组
* @returns 最大值
*/
static max(...values: number[]): number {
return Math.max(...values);
}
/**
* 计算数组的平均值
* @param values 数值数组
* @returns 平均值
*/
static average(values: number[]): number {
if (values.length === 0) return 0;
return values.reduce((sum, val) => sum + val, 0) / values.length;
}
/**
* 计算数组的中位数
* @param values 数值数组
* @returns 中位数
*/
static median(values: number[]): number {
if (values.length === 0) return 0;
const sorted = [...values].sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
}

View File

@@ -0,0 +1,630 @@
import { Vector2 } from './Vector2';
/**
* 3x3变换矩阵类
* 3x3 Transform Matrix Class
*
* 用于2D变换平移、旋转、缩放的3x3矩阵
* 使用左手坐标系(顺时针正旋转)
*
* 矩阵布局(顺时针旋转):
* [m00, m01, m02] [scaleX * cos, scaleY * sin, translateX]
* [m10, m11, m12] = [-scaleX * sin, scaleY * cos, translateY]
* [m20, m21, m22] [0, 0, 1]
*/
export class Matrix3 {
/** 矩阵元素,按行优先存储 */
public elements: Float32Array;
/**
* 创建3x3矩阵
* @param elements 矩阵元素数组(可选),默认为单位矩阵
*/
constructor(elements?: ArrayLike<number>) {
this.elements = new Float32Array(9);
if (elements) {
this.elements.set(elements);
} else {
this.identity();
}
}
// 静态常量
/** 单位矩阵 */
static readonly IDENTITY = new Matrix3([
1, 0, 0,
0, 1, 0,
0, 0, 1
]);
/** 零矩阵 */
static readonly ZERO = new Matrix3([
0, 0, 0,
0, 0, 0,
0, 0, 0
]);
// 元素访问器
/** 获取矩阵元素 */
get(row: number, col: number): number {
return this.elements[row * 3 + col];
}
/** 设置矩阵元素 */
set(row: number, col: number, value: number): this {
this.elements[row * 3 + col] = value;
return this;
}
// 快速访问器
get m00(): number { return this.elements[0]; }
set m00(value: number) { this.elements[0] = value; }
get m01(): number { return this.elements[1]; }
set m01(value: number) { this.elements[1] = value; }
get m02(): number { return this.elements[2]; }
set m02(value: number) { this.elements[2] = value; }
get m10(): number { return this.elements[3]; }
set m10(value: number) { this.elements[3] = value; }
get m11(): number { return this.elements[4]; }
set m11(value: number) { this.elements[4] = value; }
get m12(): number { return this.elements[5]; }
set m12(value: number) { this.elements[5] = value; }
get m20(): number { return this.elements[6]; }
set m20(value: number) { this.elements[6] = value; }
get m21(): number { return this.elements[7]; }
set m21(value: number) { this.elements[7] = value; }
get m22(): number { return this.elements[8]; }
set m22(value: number) { this.elements[8] = value; }
// 基础操作
/**
* 设置矩阵为单位矩阵
* @returns 当前矩阵实例(链式调用)
*/
identity(): this {
this.elements.set([
1, 0, 0,
0, 1, 0,
0, 0, 1
]);
return this;
}
/**
* 设置矩阵为零矩阵
* @returns 当前矩阵实例(链式调用)
*/
zero(): this {
this.elements.fill(0);
return this;
}
/**
* 复制另一个矩阵的值
* @param other 源矩阵
* @returns 当前矩阵实例(链式调用)
*/
copy(other: Matrix3): this {
this.elements.set(other.elements);
return this;
}
/**
* 克隆当前矩阵
* @returns 新的矩阵实例
*/
clone(): Matrix3 {
return new Matrix3(this.elements);
}
/**
* 从数组设置矩阵元素
* @param elements 矩阵元素数组
* @returns 当前矩阵实例(链式调用)
*/
fromArray(elements: ArrayLike<number>): this {
this.elements.set(elements);
return this;
}
// 矩阵运算
/**
* 矩阵加法
* @param other 另一个矩阵
* @returns 当前矩阵实例(链式调用)
*/
add(other: Matrix3): this {
for (let i = 0; i < 9; i++) {
this.elements[i] += other.elements[i];
}
return this;
}
/**
* 矩阵减法
* @param other 另一个矩阵
* @returns 当前矩阵实例(链式调用)
*/
subtract(other: Matrix3): this {
for (let i = 0; i < 9; i++) {
this.elements[i] -= other.elements[i];
}
return this;
}
/**
* 矩阵标量乘法
* @param scalar 标量
* @returns 当前矩阵实例(链式调用)
*/
multiplyScalar(scalar: number): this {
for (let i = 0; i < 9; i++) {
this.elements[i] *= scalar;
}
return this;
}
/**
* 矩阵乘法
* @param other 另一个矩阵
* @returns 当前矩阵实例(链式调用)
*/
multiply(other: Matrix3): this {
const a = this.elements;
const b = other.elements;
const result = new Float32Array(9);
result[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
result[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
result[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
result[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
result[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
result[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
result[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
result[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
result[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
this.elements.set(result);
return this;
}
/**
* 左乘另一个矩阵other * this
* @param other 左乘矩阵
* @returns 当前矩阵实例(链式调用)
*/
premultiply(other: Matrix3): this {
const a = other.elements;
const b = this.elements;
const result = new Float32Array(9);
result[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
result[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
result[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
result[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
result[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
result[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
result[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
result[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
result[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
this.elements.set(result);
return this;
}
// 变换操作
/**
* 设置为平移矩阵
* @param x X方向平移
* @param y Y方向平移
* @returns 当前矩阵实例(链式调用)
*/
makeTranslation(x: number, y: number): this {
this.elements.set([
1, 0, x,
0, 1, y,
0, 0, 1
]);
return this;
}
/**
* 设置为旋转矩阵(顺时针为正)
* Set as rotation matrix (clockwise positive)
*
* 使用左手坐标系约定:正角度 = 顺时针旋转
* Uses left-hand coordinate system: positive angle = clockwise
*
* @param angle 旋转角度(弧度)
* @returns 当前矩阵实例(链式调用)
*/
makeRotation(angle: number): this {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
// Clockwise rotation matrix
// 顺时针旋转矩阵
this.elements.set([
cos, sin, 0,
-sin, cos, 0,
0, 0, 1
]);
return this;
}
/**
* 设置为缩放矩阵
* @param scaleX X方向缩放
* @param scaleY Y方向缩放
* @returns 当前矩阵实例(链式调用)
*/
makeScale(scaleX: number, scaleY: number): this {
this.elements.set([
scaleX, 0, 0,
0, scaleY, 0,
0, 0, 1
]);
return this;
}
/**
* 复合平移
* @param x X方向平移
* @param y Y方向平移
* @returns 当前矩阵实例(链式调用)
*/
translate(x: number, y: number): this {
this.m02 += this.m00 * x + this.m01 * y;
this.m12 += this.m10 * x + this.m11 * y;
return this;
}
/**
* 复合旋转(顺时针为正)
* Composite rotation (clockwise positive)
*
* 使用左手坐标系约定:正角度 = 顺时针旋转
* Uses left-hand coordinate system: positive angle = clockwise
*
* @param angle 旋转角度(弧度)
* @returns 当前矩阵实例(链式调用)
*/
rotate(angle: number): this {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
// Clockwise rotation: multiply by [cos, sin; -sin, cos]
// 顺时针旋转
const m00 = this.m00 * cos - this.m01 * sin;
const m01 = this.m00 * sin + this.m01 * cos;
const m10 = this.m10 * cos - this.m11 * sin;
const m11 = this.m10 * sin + this.m11 * cos;
this.m00 = m00;
this.m01 = m01;
this.m10 = m10;
this.m11 = m11;
return this;
}
/**
* 复合缩放
* @param scaleX X方向缩放
* @param scaleY Y方向缩放
* @returns 当前矩阵实例(链式调用)
*/
scale(scaleX: number, scaleY: number): this {
this.m00 *= scaleX;
this.m01 *= scaleY;
this.m10 *= scaleX;
this.m11 *= scaleY;
return this;
}
// 矩阵变换
/**
* 矩阵转置
* @returns 当前矩阵实例(链式调用)
*/
transpose(): this {
const elements = this.elements;
let tmp: number;
tmp = elements[1]; elements[1] = elements[3]; elements[3] = tmp;
tmp = elements[2]; elements[2] = elements[6]; elements[6] = tmp;
tmp = elements[5]; elements[5] = elements[7]; elements[7] = tmp;
return this;
}
/**
* 计算矩阵行列式
* @returns 行列式值
*/
determinant(): number {
const e = this.elements;
return e[0] * (e[4] * e[8] - e[5] * e[7]) -
e[1] * (e[3] * e[8] - e[5] * e[6]) +
e[2] * (e[3] * e[7] - e[4] * e[6]);
}
/**
* 矩阵求逆
* @returns 当前矩阵实例(链式调用),如果矩阵不可逆则保持不变
*/
invert(): this {
const e = this.elements;
const det = this.determinant();
if (Math.abs(det) < Number.EPSILON) {
console.warn('Matrix3: 矩阵不可逆');
return this;
}
const invDet = 1 / det;
const result = new Float32Array(9);
result[0] = (e[4] * e[8] - e[5] * e[7]) * invDet;
result[1] = (e[2] * e[7] - e[1] * e[8]) * invDet;
result[2] = (e[1] * e[5] - e[2] * e[4]) * invDet;
result[3] = (e[5] * e[6] - e[3] * e[8]) * invDet;
result[4] = (e[0] * e[8] - e[2] * e[6]) * invDet;
result[5] = (e[2] * e[3] - e[0] * e[5]) * invDet;
result[6] = (e[3] * e[7] - e[4] * e[6]) * invDet;
result[7] = (e[1] * e[6] - e[0] * e[7]) * invDet;
result[8] = (e[0] * e[4] - e[1] * e[3]) * invDet;
this.elements.set(result);
return this;
}
// 向量变换
/**
* 变换向量应用完整的3x3变换
* @param vector 向量
* @returns 新的变换后的向量
*/
transformVector(vector: Vector2): Vector2 {
const x = vector.x;
const y = vector.y;
const w = this.m20 * x + this.m21 * y + this.m22;
return new Vector2(
(this.m00 * x + this.m01 * y + this.m02) / w,
(this.m10 * x + this.m11 * y + this.m12) / w
);
}
/**
* 变换向量(仅应用旋转和缩放,忽略平移)
* @param vector 向量
* @returns 新的变换后的向量
*/
transformDirection(vector: Vector2): Vector2 {
return new Vector2(
this.m00 * vector.x + this.m01 * vector.y,
this.m10 * vector.x + this.m11 * vector.y
);
}
/**
* 批量变换向量数组
* @param vectors 向量数组
* @returns 变换后的向量数组
*/
transformVectors(vectors: Vector2[]): Vector2[] {
return vectors.map((v) => this.transformVector(v));
}
// 属性提取
/**
* 获取平移分量
* @returns 平移向量
*/
getTranslation(): Vector2 {
return new Vector2(this.m02, this.m12);
}
/**
* 获取旋转角度(顺时针为正)
* Get rotation angle (clockwise positive)
* @returns 旋转角度(弧度)
*/
getRotation(): number {
// For clockwise rotation matrix [cos, sin; -sin, cos]
// m00 = cos, m01 = sin, so atan2(m01, m00) = θ
// 顺时针旋转矩阵:从 m01 和 m00 提取角度
return Math.atan2(this.m01, this.m00);
}
/**
* 获取缩放分量
* @returns 缩放向量
*/
getScale(): Vector2 {
const scaleX = Math.sqrt(this.m00 * this.m00 + this.m10 * this.m10);
const scaleY = Math.sqrt(this.m01 * this.m01 + this.m11 * this.m11);
// 检查是否有反转
const det = this.determinant();
if (det < 0) {
return new Vector2(-scaleX, scaleY);
}
return new Vector2(scaleX, scaleY);
}
/**
* 分解变换矩阵为平移、旋转、缩放分量
* @returns {translation, rotation, scale}
*/
decompose(): { translation: Vector2; rotation: number; scale: Vector2 } {
return {
translation: this.getTranslation(),
rotation: this.getRotation(),
scale: this.getScale()
};
}
// 比较操作
/**
* 检查两个矩阵是否相等
* @param other 另一个矩阵
* @param epsilon 容差默认为Number.EPSILON
* @returns 是否相等
*/
equals(other: Matrix3, epsilon: number = Number.EPSILON): boolean {
for (let i = 0; i < 9; i++) {
if (Math.abs(this.elements[i] - other.elements[i]) >= epsilon) {
return false;
}
}
return true;
}
/**
* 检查两个矩阵是否完全相等
* @param other 另一个矩阵
* @returns 是否完全相等
*/
exactEquals(other: Matrix3): boolean {
for (let i = 0; i < 9; i++) {
if (this.elements[i] !== other.elements[i]) {
return false;
}
}
return true;
}
/**
* 检查是否为单位矩阵
* @param epsilon 容差默认为Number.EPSILON
* @returns 是否为单位矩阵
*/
isIdentity(epsilon: number = Number.EPSILON): boolean {
return this.equals(Matrix3.IDENTITY, epsilon);
}
// 静态方法
/**
* 矩阵乘法(静态方法)
* @param a 矩阵a
* @param b 矩阵b
* @returns 新的结果矩阵
*/
static multiply(a: Matrix3, b: Matrix3): Matrix3 {
return a.clone().multiply(b);
}
/**
* 创建平移矩阵(静态方法)
* @param x X方向平移
* @param y Y方向平移
* @returns 新的平移矩阵
*/
static translation(x: number, y: number): Matrix3 {
return new Matrix3().makeTranslation(x, y);
}
/**
* 创建旋转矩阵(静态方法)
* @param angle 旋转角度(弧度)
* @returns 新的旋转矩阵
*/
static rotation(angle: number): Matrix3 {
return new Matrix3().makeRotation(angle);
}
/**
* 创建缩放矩阵(静态方法)
* @param scaleX X方向缩放
* @param scaleY Y方向缩放
* @returns 新的缩放矩阵
*/
static scale(scaleX: number, scaleY: number): Matrix3 {
return new Matrix3().makeScale(scaleX, scaleY);
}
/**
* 创建TRS平移-旋转-缩放)变换矩阵(顺时针为正)
* Create TRS (Translate-Rotate-Scale) matrix (clockwise positive)
*
* 使用左手坐标系约定:正角度 = 顺时针旋转
* Uses left-hand coordinate system: positive angle = clockwise
*
* @param translation 平移向量
* @param rotation 旋转角度(弧度)
* @param scale 缩放向量
* @returns 新的TRS矩阵
*/
static TRS(translation: Vector2, rotation: number, scale: Vector2): Matrix3 {
const cos = Math.cos(rotation);
const sin = Math.sin(rotation);
// Clockwise rotation matrix with scale
// 带缩放的顺时针旋转矩阵
return new Matrix3([
scale.x * cos, scale.y * sin, translation.x,
-scale.x * sin, scale.y * cos, translation.y,
0, 0, 1
]);
}
// 字符串转换
/**
* 转换为字符串
* @returns 字符串表示
*/
toString(): string {
const e = this.elements;
return 'Matrix3(\n' +
` ${e[0].toFixed(3)}, ${e[1].toFixed(3)}, ${e[2].toFixed(3)}\n` +
` ${e[3].toFixed(3)}, ${e[4].toFixed(3)}, ${e[5].toFixed(3)}\n` +
` ${e[6].toFixed(3)}, ${e[7].toFixed(3)}, ${e[8].toFixed(3)}\n` +
')';
}
/**
* 转换为数组
* @returns 矩阵元素数组
*/
toArray(): number[] {
return Array.from(this.elements);
}
/**
* 转换为CSS transform字符串
* @returns CSS transform字符串
*/
toCSSTransform(): string {
const e = this.elements;
return `matrix(${e[0]}, ${e[3]}, ${e[1]}, ${e[4]}, ${e[2]}, ${e[5]})`;
}
}

View File

@@ -0,0 +1,520 @@
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
];
}
}

View File

@@ -0,0 +1,562 @@
/**
* 2D 向量数据接口
*
* 轻量级数据结构,用于组件属性和序列化。
* Lightweight data structure for component properties and serialization.
*/
export interface IVector2 {
x: number;
y: number;
}
/**
* 2D向量类
*
* 提供完整的2D向量运算功能包括
* - 基础运算(加减乘除)
* - 向量运算(点积、叉积、归一化)
* - 几何运算(距离、角度、投影)
* - 变换操作(旋转、反射、插值)
*/
export class Vector2 implements IVector2 {
/** X分量 */
public x: number;
/** Y分量 */
public y: number;
/**
* 创建2D向量
* @param x X分量默认为0
* @param y Y分量默认为0
*/
constructor(x: number = 0, y: number = 0) {
this.x = x;
this.y = y;
}
// 静态常量
/** 零向量 (0, 0) */
static readonly ZERO = new Vector2(0, 0);
/** 单位向量 (1, 1) */
static readonly ONE = new Vector2(1, 1);
/** 右方向向量 (1, 0) */
static readonly RIGHT = new Vector2(1, 0);
/** 左方向向量 (-1, 0) */
static readonly LEFT = new Vector2(-1, 0);
/** 上方向向量 (0, 1) */
static readonly UP = new Vector2(0, 1);
/** 下方向向量 (0, -1) */
static readonly DOWN = new Vector2(0, -1);
// 基础属性
/**
* 获取向量长度(模)
*/
get length(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
/**
* 获取向量长度的平方
*/
get lengthSquared(): number {
return this.x * this.x + this.y * this.y;
}
/**
* 获取向量角度(弧度)
*/
get angle(): number {
return Math.atan2(this.y, this.x);
}
/**
* 检查是否为零向量
*/
get isZero(): boolean {
return this.x === 0 && this.y === 0;
}
/**
* 检查是否为单位向量
*/
get isUnit(): boolean {
const lenSq = this.lengthSquared;
return Math.abs(lenSq - 1) < Number.EPSILON;
}
// 基础运算
/**
* 设置向量分量
* @param x X分量
* @param y Y分量
* @returns 当前向量实例(链式调用)
*/
set(x: number, y: number): this {
this.x = x;
this.y = y;
return this;
}
/**
* 复制另一个向量的值
* @param other 源向量
* @returns 当前向量实例(链式调用)
*/
copy(other: Vector2): this {
this.x = other.x;
this.y = other.y;
return this;
}
/**
* 克隆当前向量
* @returns 新的向量实例
*/
clone(): Vector2 {
return new Vector2(this.x, this.y);
}
/**
* 向量加法
* @param other 另一个向量
* @returns 当前向量实例(链式调用)
*/
add(other: Vector2): this {
this.x += other.x;
this.y += other.y;
return this;
}
/**
* 向量减法
* @param other 另一个向量
* @returns 当前向量实例(链式调用)
*/
subtract(other: Vector2): this {
this.x -= other.x;
this.y -= other.y;
return this;
}
/**
* 向量数乘
* @param scalar 标量
* @returns 当前向量实例(链式调用)
*/
multiply(scalar: number): this {
this.x *= scalar;
this.y *= scalar;
return this;
}
/**
* 向量数除
* @param scalar 标量
* @returns 当前向量实例(链式调用)
*/
divide(scalar: number): this {
if (scalar === 0) {
throw new Error('不能除以零');
}
this.x /= scalar;
this.y /= scalar;
return this;
}
/**
* 向量取反
* @returns 当前向量实例(链式调用)
*/
negate(): this {
this.x = -this.x;
this.y = -this.y;
return this;
}
// 向量运算
/**
* 计算与另一个向量的点积
* @param other 另一个向量
* @returns 点积值
*/
dot(other: Vector2): number {
return this.x * other.x + this.y * other.y;
}
/**
* 计算与另一个向量的叉积2D中返回标量
* @param other 另一个向量
* @returns 叉积值
*/
cross(other: Vector2): number {
return this.x * other.y - this.y * other.x;
}
/**
* 向量归一化(转换为单位向量)
* @returns 当前向量实例(链式调用)
*/
normalize(): this {
const len = this.length;
if (len === 0) {
return this;
}
return this.divide(len);
}
/**
* 获取归一化后的向量(不修改原向量)
* @returns 新的单位向量
*/
normalized(): Vector2 {
return this.clone().normalize();
}
// 几何运算
/**
* 计算到另一个向量的距离
* @param other 另一个向量
* @returns 距离值
*/
distanceTo(other: Vector2): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 计算到另一个向量的距离平方
* @param other 另一个向量
* @returns 距离平方值
*/
distanceToSquared(other: Vector2): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
return dx * dx + dy * dy;
}
/**
* 计算与另一个向量的夹角(弧度)
* @param other 另一个向量
* @returns 夹角0到π
*/
angleTo(other: Vector2): number {
const dot = this.dot(other);
const lenProduct = this.length * other.length;
if (lenProduct === 0) return 0;
return Math.acos(Math.max(-1, Math.min(1, dot / lenProduct)));
}
/**
* 计算向量在另一个向量上的投影
* @param onto 投影目标向量
* @returns 新的投影向量
*/
projectOnto(onto: Vector2): Vector2 {
const dot = this.dot(onto);
const lenSq = onto.lengthSquared;
if (lenSq === 0) return new Vector2();
return onto.clone().multiply(dot / lenSq);
}
/**
* 计算向量在另一个向量上的投影长度
* @param onto 投影目标向量
* @returns 投影长度(带符号)
*/
projectOntoLength(onto: Vector2): number {
const len = onto.length;
if (len === 0) return 0;
return this.dot(onto) / len;
}
/**
* 获取垂直向量顺时针旋转90度
* Get perpendicular vector (clockwise 90 degrees)
* @returns 新的垂直向量
*/
perpendicular(): Vector2 {
// Clockwise 90° rotation: (x, y) -> (y, -x)
// 顺时针旋转 90°
return new Vector2(this.y, -this.x);
}
// 变换操作
/**
* 向量旋转(顺时针为正)
* Rotate vector (clockwise positive)
*
* 使用左手坐标系约定:正角度 = 顺时针旋转
* Uses left-hand coordinate system: positive angle = clockwise
*
* @param angle 旋转角度(弧度)
* @returns 当前向量实例(链式调用)
*/
rotate(angle: number): this {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
// Clockwise rotation: x' = x*cos + y*sin, y' = -x*sin + y*cos
// 顺时针旋转公式
const x = this.x * cos + this.y * sin;
const y = -this.x * sin + this.y * cos;
this.x = x;
this.y = y;
return this;
}
/**
* 获取旋转后的向量(不修改原向量)
* @param angle 旋转角度(弧度)
* @returns 新的旋转后向量
*/
rotated(angle: number): Vector2 {
return this.clone().rotate(angle);
}
/**
* 围绕一个点旋转
* @param center 旋转中心点
* @param angle 旋转角度(弧度)
* @returns 当前向量实例(链式调用)
*/
rotateAround(center: Vector2, angle: number): this {
return this.subtract(center).rotate(angle).add(center);
}
/**
* 反射向量(关于法线)
* @param normal 法线向量(应为单位向量)
* @returns 当前向量实例(链式调用)
*/
reflect(normal: Vector2): this {
const dot = this.dot(normal);
this.x -= 2 * dot * normal.x;
this.y -= 2 * dot * normal.y;
return this;
}
/**
* 获取反射后的向量(不修改原向量)
* @param normal 法线向量(应为单位向量)
* @returns 新的反射向量
*/
reflected(normal: Vector2): Vector2 {
return this.clone().reflect(normal);
}
// 插值和限制
/**
* 线性插值
* @param target 目标向量
* @param t 插值参数0到1
* @returns 当前向量实例(链式调用)
*/
lerp(target: Vector2, t: number): this {
this.x += (target.x - this.x) * t;
this.y += (target.y - this.y) * t;
return this;
}
/**
* 限制向量长度
* @param maxLength 最大长度
* @returns 当前向量实例(链式调用)
*/
clampLength(maxLength: number): this {
const lenSq = this.lengthSquared;
if (lenSq > maxLength * maxLength) {
return this.normalize().multiply(maxLength);
}
return this;
}
/**
* 限制向量分量
* @param min 最小值向量
* @param max 最大值向量
* @returns 当前向量实例(链式调用)
*/
clamp(min: Vector2, max: Vector2): this {
this.x = Math.max(min.x, Math.min(max.x, this.x));
this.y = Math.max(min.y, Math.min(max.y, this.y));
return this;
}
// 比较操作
/**
* 检查两个向量是否相等
* @param other 另一个向量
* @param epsilon 容差默认为Number.EPSILON
* @returns 是否相等
*/
equals(other: Vector2, epsilon: number = Number.EPSILON): boolean {
return Math.abs(this.x - other.x) < epsilon &&
Math.abs(this.y - other.y) < epsilon;
}
/**
* 检查两个向量是否完全相等
* @param other 另一个向量
* @returns 是否完全相等
*/
exactEquals(other: Vector2): boolean {
return this.x === other.x && this.y === other.y;
}
// 静态方法
/**
* 向量加法(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的结果向量
*/
static add(a: Vector2, b: Vector2): Vector2 {
return new Vector2(a.x + b.x, a.y + b.y);
}
/**
* 向量减法(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的结果向量
*/
static subtract(a: Vector2, b: Vector2): Vector2 {
return new Vector2(a.x - b.x, a.y - b.y);
}
/**
* 向量数乘(静态方法)
* @param vector 向量
* @param scalar 标量
* @returns 新的结果向量
*/
static multiply(vector: Vector2, scalar: number): Vector2 {
return new Vector2(vector.x * scalar, vector.y * scalar);
}
/**
* 向量点积(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 点积值
*/
static dot(a: Vector2, b: Vector2): number {
return a.x * b.x + a.y * b.y;
}
/**
* 向量叉积(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 叉积值
*/
static cross(a: Vector2, b: Vector2): number {
return a.x * b.y - a.y * b.x;
}
/**
* 计算两点间距离(静态方法)
* @param a 点a
* @param b 点b
* @returns 距离值
*/
static distance(a: Vector2, b: Vector2): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 线性插值(静态方法)
* @param a 起始向量
* @param b 目标向量
* @param t 插值参数0到1
* @returns 新的插值结果向量
*/
static lerp(a: Vector2, b: Vector2, t: number): Vector2 {
return new Vector2(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t
);
}
/**
* 从角度创建单位向量(静态方法)
* @param angle 角度(弧度)
* @returns 新的单位向量
*/
static fromAngle(angle: number): Vector2 {
return new Vector2(Math.cos(angle), Math.sin(angle));
}
/**
* 从极坐标创建向量(静态方法)
* @param length 长度
* @param angle 角度(弧度)
* @returns 新的向量
*/
static fromPolar(length: number, angle: number): Vector2 {
return new Vector2(length * Math.cos(angle), length * Math.sin(angle));
}
/**
* 获取两个向量中的最小分量向量(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的最小分量向量
*/
static min(a: Vector2, b: Vector2): Vector2 {
return new Vector2(Math.min(a.x, b.x), Math.min(a.y, b.y));
}
/**
* 获取两个向量中的最大分量向量(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的最大分量向量
*/
static max(a: Vector2, b: Vector2): Vector2 {
return new Vector2(Math.max(a.x, b.x), Math.max(a.y, b.y));
}
// 字符串转换
/**
* 转换为字符串
* @returns 字符串表示
*/
toString(): string {
return `Vector2(${this.x.toFixed(3)}, ${this.y.toFixed(3)})`;
}
/**
* 转换为数组
* @returns [x, y] 数组
*/
toArray(): [number, number] {
return [this.x, this.y];
}
/**
* 转换为普通对象
* @returns {x, y} 对象
*/
toObject(): { x: number; y: number } {
return { x: this.x, y: this.y };
}
}

View File

@@ -0,0 +1,452 @@
/**
* 3D 向量数据接口
*
* 轻量级数据结构,用于组件属性和序列化。
* Lightweight data structure for component properties and serialization.
*/
export interface IVector3 {
x: number;
y: number;
z: number;
}
/**
* 3D向量类
*
* 提供完整的3D向量运算功能包括
* - 基础运算(加减乘除)
* - 向量运算(点积、叉积、归一化)
* - 几何运算(距离、角度、投影)
* - 变换操作(旋转、反射、插值)
*/
export class Vector3 implements IVector3 {
/** X分量 */
public x: number;
/** Y分量 */
public y: number;
/** Z分量 */
public z: number;
/**
* 创建3D向量
* @param x X分量默认为0
* @param y Y分量默认为0
* @param z Z分量默认为0
*/
constructor(x: number = 0, y: number = 0, z: number = 0) {
this.x = x;
this.y = y;
this.z = z;
}
// 静态常量
/** 零向量 (0, 0, 0) */
static readonly ZERO = new Vector3(0, 0, 0);
/** 单位向量 (1, 1, 1) */
static readonly ONE = new Vector3(1, 1, 1);
/** 右方向向量 (1, 0, 0) */
static readonly RIGHT = new Vector3(1, 0, 0);
/** 左方向向量 (-1, 0, 0) */
static readonly LEFT = new Vector3(-1, 0, 0);
/** 上方向向量 (0, 1, 0) */
static readonly UP = new Vector3(0, 1, 0);
/** 下方向向量 (0, -1, 0) */
static readonly DOWN = new Vector3(0, -1, 0);
/** 前方向向量 (0, 0, 1) */
static readonly FORWARD = new Vector3(0, 0, 1);
/** 后方向向量 (0, 0, -1) */
static readonly BACK = new Vector3(0, 0, -1);
// 基础属性
/**
* 获取向量长度(模)
*/
get length(): number {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
/**
* 获取向量长度的平方
*/
get lengthSquared(): number {
return this.x * this.x + this.y * this.y + this.z * this.z;
}
/**
* 检查是否为零向量
*/
get isZero(): boolean {
return this.x === 0 && this.y === 0 && this.z === 0;
}
/**
* 检查是否为单位向量
*/
get isUnit(): boolean {
const lenSq = this.lengthSquared;
return Math.abs(lenSq - 1) < Number.EPSILON;
}
// 基础运算
/**
* 设置向量分量
* @param x X分量
* @param y Y分量
* @param z Z分量
* @returns 当前向量实例(链式调用)
*/
set(x: number, y: number, z: number): this {
this.x = x;
this.y = y;
this.z = z;
return this;
}
/**
* 复制另一个向量的值
* @param other 源向量
* @returns 当前向量实例(链式调用)
*/
copy(other: Vector3): this {
this.x = other.x;
this.y = other.y;
this.z = other.z;
return this;
}
/**
* 克隆当前向量
* @returns 新的向量实例
*/
clone(): Vector3 {
return new Vector3(this.x, this.y, this.z);
}
/**
* 向量加法
* @param other 另一个向量
* @returns 当前向量实例(链式调用)
*/
add(other: Vector3): this {
this.x += other.x;
this.y += other.y;
this.z += other.z;
return this;
}
/**
* 向量减法
* @param other 另一个向量
* @returns 当前向量实例(链式调用)
*/
subtract(other: Vector3): this {
this.x -= other.x;
this.y -= other.y;
this.z -= other.z;
return this;
}
/**
* 向量数乘
* @param scalar 标量
* @returns 当前向量实例(链式调用)
*/
multiply(scalar: number): this {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
return this;
}
/**
* 向量数除
* @param scalar 标量
* @returns 当前向量实例(链式调用)
*/
divide(scalar: number): this {
if (scalar === 0) {
throw new Error('不能除以零');
}
this.x /= scalar;
this.y /= scalar;
this.z /= scalar;
return this;
}
/**
* 向量取反
* @returns 当前向量实例(链式调用)
*/
negate(): this {
this.x = -this.x;
this.y = -this.y;
this.z = -this.z;
return this;
}
// 向量运算
/**
* 计算与另一个向量的点积
* @param other 另一个向量
* @returns 点积值
*/
dot(other: Vector3): number {
return this.x * other.x + this.y * other.y + this.z * other.z;
}
/**
* 计算与另一个向量的叉积
* @param other 另一个向量
* @returns 新的叉积向量
*/
cross(other: Vector3): Vector3 {
return new Vector3(
this.y * other.z - this.z * other.y,
this.z * other.x - this.x * other.z,
this.x * other.y - this.y * other.x
);
}
/**
* 向量归一化(转换为单位向量)
* @returns 当前向量实例(链式调用)
*/
normalize(): this {
const len = this.length;
if (len === 0) {
return this;
}
return this.divide(len);
}
/**
* 获取归一化后的向量(不修改原向量)
* @returns 新的单位向量
*/
normalized(): Vector3 {
return this.clone().normalize();
}
// 几何运算
/**
* 计算到另一个向量的距离
* @param other 另一个向量
* @returns 距离值
*/
distanceTo(other: Vector3): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
const dz = this.z - other.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
/**
* 计算到另一个向量的距离平方
* @param other 另一个向量
* @returns 距离平方值
*/
distanceToSquared(other: Vector3): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
const dz = this.z - other.z;
return dx * dx + dy * dy + dz * dz;
}
/**
* 计算与另一个向量的夹角(弧度)
* @param other 另一个向量
* @returns 夹角0到π
*/
angleTo(other: Vector3): number {
const dot = this.dot(other);
const lenProduct = this.length * other.length;
if (lenProduct === 0) return 0;
return Math.acos(Math.max(-1, Math.min(1, dot / lenProduct)));
}
/**
* 计算向量在另一个向量上的投影
* @param onto 投影目标向量
* @returns 新的投影向量
*/
projectOnto(onto: Vector3): Vector3 {
const dot = this.dot(onto);
const lenSq = onto.lengthSquared;
if (lenSq === 0) return new Vector3();
return onto.clone().multiply(dot / lenSq);
}
// 插值和限制
/**
* 线性插值
* @param target 目标向量
* @param t 插值参数0到1
* @returns 当前向量实例(链式调用)
*/
lerp(target: Vector3, t: number): this {
this.x += (target.x - this.x) * t;
this.y += (target.y - this.y) * t;
this.z += (target.z - this.z) * t;
return this;
}
/**
* 限制向量长度
* @param maxLength 最大长度
* @returns 当前向量实例(链式调用)
*/
clampLength(maxLength: number): this {
const lenSq = this.lengthSquared;
if (lenSq > maxLength * maxLength) {
return this.normalize().multiply(maxLength);
}
return this;
}
// 比较操作
/**
* 检查两个向量是否相等
* @param other 另一个向量
* @param epsilon 容差默认为Number.EPSILON
* @returns 是否相等
*/
equals(other: Vector3, epsilon: number = Number.EPSILON): boolean {
return Math.abs(this.x - other.x) < epsilon &&
Math.abs(this.y - other.y) < epsilon &&
Math.abs(this.z - other.z) < epsilon;
}
/**
* 检查两个向量是否完全相等
* @param other 另一个向量
* @returns 是否完全相等
*/
exactEquals(other: Vector3): boolean {
return this.x === other.x && this.y === other.y && this.z === other.z;
}
// 静态方法
/**
* 向量加法(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的结果向量
*/
static add(a: Vector3, b: Vector3): Vector3 {
return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
}
/**
* 向量减法(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的结果向量
*/
static subtract(a: Vector3, b: Vector3): Vector3 {
return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
}
/**
* 向量数乘(静态方法)
* @param vector 向量
* @param scalar 标量
* @returns 新的结果向量
*/
static multiply(vector: Vector3, scalar: number): Vector3 {
return new Vector3(vector.x * scalar, vector.y * scalar, vector.z * scalar);
}
/**
* 向量点积(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 点积值
*/
static dot(a: Vector3, b: Vector3): number {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
/**
* 向量叉积(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的叉积向量
*/
static cross(a: Vector3, b: Vector3): Vector3 {
return new Vector3(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
/**
* 计算两点间距离(静态方法)
* @param a 点a
* @param b 点b
* @returns 距离值
*/
static distance(a: Vector3, b: Vector3): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
const dz = a.z - b.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
/**
* 线性插值(静态方法)
* @param a 起始向量
* @param b 目标向量
* @param t 插值参数0到1
* @returns 新的插值结果向量
*/
static lerp(a: Vector3, b: Vector3, t: number): Vector3 {
return new Vector3(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t,
a.z + (b.z - a.z) * t
);
}
// 字符串转换
/**
* 转换为字符串
* @returns 字符串表示
*/
toString(): string {
return `Vector3(${this.x.toFixed(3)}, ${this.y.toFixed(3)}, ${this.z.toFixed(3)})`;
}
/**
* 转换为数组
* @returns [x, y, z] 数组
*/
toArray(): [number, number, number] {
return [this.x, this.y, this.z];
}
/**
* 转换为普通对象
* @returns {x, y, z} 对象
*/
toObject(): { x: number; y: number; z: number } {
return { x: this.x, y: this.y, z: this.z };
}
}

View File

@@ -0,0 +1,29 @@
/**
* ECS Framework Math Library
*
* 2D数学库为游戏开发提供完整的数学工具
* - 基础数学类(向量、矩阵、几何形状)
* - 碰撞检测算法
* - 动画插值和缓动函数
* - 数学工具函数
* - 颜色工具类
*/
// 核心数学类
export { Vector2, type IVector2 } from './Vector2';
export { Vector3, type IVector3 } from './Vector3';
export { Matrix3 } from './Matrix3';
export { Rectangle } from './Rectangle';
export { Circle } from './Circle';
// 数学工具
export { MathUtils } from './MathUtils';
// 颜色工具
export { Color, type RGBA, type HSL } from './Color';
// 碰撞检测
export * from './Collision';
// 动画和插值
export * from './Animation';