新增math库
This commit is contained in:
464
packages/math/src/Animation/Easing.ts
Normal file
464
packages/math/src/Animation/Easing.ts
Normal 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;
|
||||
}
|
||||
411
packages/math/src/Animation/Interpolation.ts
Normal file
411
packages/math/src/Animation/Interpolation.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
13
packages/math/src/Animation/index.ts
Normal file
13
packages/math/src/Animation/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 动画和插值模块
|
||||
*
|
||||
* 提供缓动函数和各种插值功能
|
||||
*/
|
||||
|
||||
export { Easing } from './Easing';
|
||||
export {
|
||||
Interpolation,
|
||||
CachedInterpolator,
|
||||
type InterpolatorFunction,
|
||||
type Keyframe
|
||||
} from './Interpolation';
|
||||
594
packages/math/src/Circle.ts
Normal file
594
packages/math/src/Circle.ts
Normal 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 };
|
||||
}
|
||||
}
|
||||
445
packages/math/src/Collision/CollisionDetector.ts
Normal file
445
packages/math/src/Collision/CollisionDetector.ts
Normal 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;
|
||||
let 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);
|
||||
|
||||
// 选择最近的正距离
|
||||
let 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;
|
||||
}
|
||||
}
|
||||
7
packages/math/src/Collision/index.ts
Normal file
7
packages/math/src/Collision/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 碰撞检测模块
|
||||
*
|
||||
* 提供各种几何体间的碰撞检测功能
|
||||
*/
|
||||
|
||||
export { CollisionDetector, type CollisionInfo } from './CollisionDetector';
|
||||
567
packages/math/src/MathUtils.ts
Normal file
567
packages/math/src/MathUtils.ts
Normal 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 {
|
||||
let 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];
|
||||
}
|
||||
}
|
||||
602
packages/math/src/Matrix3.ts
Normal file
602
packages/math/src/Matrix3.ts
Normal file
@@ -0,0 +1,602 @@
|
||||
import { Vector2 } from './Vector2';
|
||||
|
||||
/**
|
||||
* 3x3变换矩阵类
|
||||
*
|
||||
* 用于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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置为旋转矩阵
|
||||
* @param angle 旋转角度(弧度)
|
||||
* @returns 当前矩阵实例(链式调用)
|
||||
*/
|
||||
makeRotation(angle: number): this {
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复合旋转
|
||||
* @param angle 旋转角度(弧度)
|
||||
* @returns 当前矩阵实例(链式调用)
|
||||
*/
|
||||
rotate(angle: number): this {
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取旋转角度
|
||||
* @returns 旋转角度(弧度)
|
||||
*/
|
||||
getRotation(): number {
|
||||
return Math.atan2(this.m10, 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(平移-旋转-缩放)变换矩阵
|
||||
* @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);
|
||||
|
||||
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]})`;
|
||||
}
|
||||
}
|
||||
520
packages/math/src/Rectangle.ts
Normal file
520
packages/math/src/Rectangle.ts
Normal 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
|
||||
];
|
||||
}
|
||||
}
|
||||
541
packages/math/src/Vector2.ts
Normal file
541
packages/math/src/Vector2.ts
Normal file
@@ -0,0 +1,541 @@
|
||||
/**
|
||||
* 2D向量类
|
||||
*
|
||||
* 提供完整的2D向量运算功能,包括:
|
||||
* - 基础运算(加减乘除)
|
||||
* - 向量运算(点积、叉积、归一化)
|
||||
* - 几何运算(距离、角度、投影)
|
||||
* - 变换操作(旋转、反射、插值)
|
||||
*/
|
||||
export class Vector2 {
|
||||
/** 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度)
|
||||
* @returns 新的垂直向量
|
||||
*/
|
||||
perpendicular(): Vector2 {
|
||||
return new Vector2(-this.y, this.x);
|
||||
}
|
||||
|
||||
// 变换操作
|
||||
|
||||
/**
|
||||
* 向量旋转
|
||||
* @param angle 旋转角度(弧度)
|
||||
* @returns 当前向量实例(链式调用)
|
||||
*/
|
||||
rotate(angle: number): this {
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
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 };
|
||||
}
|
||||
}
|
||||
24
packages/math/src/index.ts
Normal file
24
packages/math/src/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* ECS Framework Math Library
|
||||
*
|
||||
* 2D数学库,为游戏开发提供完整的数学工具
|
||||
* - 基础数学类(向量、矩阵、几何形状)
|
||||
* - 碰撞检测算法
|
||||
* - 动画插值和缓动函数
|
||||
* - 数学工具函数
|
||||
*/
|
||||
|
||||
// 核心数学类
|
||||
export { Vector2 } from './Vector2';
|
||||
export { Matrix3 } from './Matrix3';
|
||||
export { Rectangle } from './Rectangle';
|
||||
export { Circle } from './Circle';
|
||||
|
||||
// 数学工具
|
||||
export { MathUtils } from './MathUtils';
|
||||
|
||||
// 碰撞检测
|
||||
export * from './Collision';
|
||||
|
||||
// 动画和插值
|
||||
export * from './Animation';
|
||||
Reference in New Issue
Block a user