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