新增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';
|
||||
Reference in New Issue
Block a user