Files
esengine/packages/framework/math/src/MathUtils.ts

568 lines
14 KiB
TypeScript
Raw Normal View History

2025-08-10 16:00:02 +08:00
import { Vector2 } from './Vector2';
/**
*
*
2025-08-10 16:00:02 +08:00
*
*/
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;
// 角度转换
/**
2025-08-10 16:00:02 +08:00
*
* @param degrees
* @returns
*/
static degToRad(degrees: number): number {
return degrees * MathUtils.DEG_TO_RAD;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param radians
* @returns
*/
static radToDeg(radians: number): number {
return radians * MathUtils.RAD_TO_DEG;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
* [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;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
* (-π, π]
* @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;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param from
* @param to
* @returns -ππ
*/
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-08-10 16:00:02 +08:00
*
* @param from
* @param to
* @param t 01
* @returns
*/
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-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param value
* @param min
* @param max
* @returns
*/
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-08-10 16:00:02 +08:00
* 01
* @param value
* @returns
*/
static clamp01(value: number): number {
return Math.max(0, Math.min(1, value));
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
* 线
* @param a
* @param b
* @param t 01
* @returns
*/
static lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
* 线
* @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);
2025-08-10 16:00:02 +08:00
}
/**
2025-08-10 16:00:02 +08:00
*
* @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);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
* Hermite插值
* @param t 01
* @returns 01
*/
static smoothStep(t: number): number {
t = MathUtils.clamp01(t);
return t * t * (3 - 2 * t);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t 01
* @returns 01
*/
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-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @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;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param value
* @param epsilon EPSILON
* @returns
*/
static isZero(value: number, epsilon: number = MathUtils.EPSILON): boolean {
return Math.abs(value) < epsilon;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param value
* @returns 1-10
*/
static sign(value: number): number {
return value > 0 ? 1 : value < 0 ? -1 : 0;
}
2025-08-10 16:00:02 +08:00
// 随机数生成
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param min
* @param max
* @returns
*/
static random(min: number = 0, max: number = 1): number {
return Math.random() * (max - min) + min;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param min
* @param max
* @returns
*/
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-08-10 16:00:02 +08:00
*
* @param array
* @returns
*/
static randomChoice<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param probability true的概率010.5
* @returns
*/
static randomBoolean(probability: number = 0.5): boolean {
return Math.random() < probability;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @returns
*/
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-08-10 16:00:02 +08:00
*
* @returns
*/
static randomOnUnitCircle(): Vector2 {
const angle = Math.random() * MathUtils.TWO_PI;
return Vector2.fromAngle(angle);
}
2025-08-10 16:00:02 +08:00
// 数学函数
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param value
* @returns
*/
static fastInverseSqrt(value: number): number {
2025-08-10 16:00:02 +08:00
// 简化版本现代JavaScript引擎优化很好
return 1 / Math.sqrt(value);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @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;
2025-08-10 16:00:02 +08:00
return Math.pow(base, exponent);
}
/**
2025-08-10 16:00:02 +08:00
*
* @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;
2025-08-10 16:00:02 +08:00
}
/**
2025-08-10 16:00:02 +08:00
*
* @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;
2025-08-10 16:00:02 +08:00
}
/**
2025-08-10 16:00:02 +08:00
*
* @param a a
* @param b b
* @returns
*/
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-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @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;
2025-08-10 16:00:02 +08:00
}
/**
2025-08-10 16:00:02 +08:00
*
* @param first
* @param last
* @param count
* @returns
*/
static arithmeticSum(first: number, last: number, count: number): number {
return (first + last) * count * 0.5;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @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);
2025-08-10 16:00:02 +08:00
}
// 曲线和插值
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
* 线
* @param p0 0
* @param p1 1
* @param p2 2
* @param t 01
* @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
);
}
/**
2025-08-10 16:00:02 +08:00
* 线
* @param p0 0
* @param p1 1
* @param p2 2
* @param p3 3
* @param t 01
* @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
);
}
/**
2025-08-10 16:00:02 +08:00
* Catmull-Rom样条插值
* @param p0 0
* @param p1 1
* @param p2 2
* @param p3 3
* @param t 01
* @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) +
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
);
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
);
return new Vector2(x, y);
}
2025-08-10 16:00:02 +08:00
// 噪声函数
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param x X
* @param y Y
* @param seed
* @returns 01
*/
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-08-10 16:00:02 +08:00
*
* @param x X
* @param y Y
* @param seed
* @returns 01
*/
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
*/
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-08-10 16:00:02 +08:00
*
* @param value
* @param min
* @param max
* @returns
*/
static inRange(value: number, min: number, max: number): boolean {
return value >= min && value <= max;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param values
* @returns
*/
static min(...values: number[]): number {
return Math.min(...values);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param values
* @returns
*/
static max(...values: number[]): number {
return Math.max(...values);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param values
* @returns
*/
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-08-10 16:00:02 +08:00
*
* @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];
}
}