import { vec3, quat, mat3, mat4, glMatrix } from 'gl-matrix';
import { Utils } from "../common/Utils";
import { BezierPathGenerator } from "./BezierPathGenerator.js";
// Rewrite all glm create interface, we must NOT use float32Array due to the precision is not enough
glMatrix.setMatrixArrayType(Array);
let _vec3_0 = vec3.create();
let _vec3_1 = vec3.create();
let _vec3_2 = vec3.create();
let _vec3_3 = vec3.create();
let _vec3_4 = vec3.create();
let _mat4 = mat4.create();
let _mat4_1 = mat4.create();
let _mat4_2 = mat4.create();
let _position = vec3.create();
let _quat = quat.create();
let _scale = vec3.create();
let _original_scale = [1, 1, 1];
let _identity_mat4 = mat4.identity([]);
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
let lut = [];
for (var i = 0; i < 256; i++) {
lut[i] = (i < 16 ? '0' : '') + (i).toString(16).toUpperCase();
}
function _localToTextureCoord(target, localPos, textureSize) {
// 局部坐标转纹理坐标
const [x1, y1, z1] = textureSize;
target[0] = (localPos[0] + 0.5) * x1;
target[1] = (-localPos[2] + 0.5) * y1;
target[2] = (localPos[1] + 0.5) * z1;
return target;
}
function _textureCoordToLocal(target, textureCoord, textureSize) {
const [x1, y1, z1] = textureSize;
// 纹理坐标转局部坐标 (归一化到[-0.5,0.5])
target[0] = textureCoord[0] / x1 - 0.5;
target[1] = textureCoord[2] / z1 - 0.5;
target[2] = -(textureCoord[1] / y1 - 0.5);
return target;
}
/**
* 将欧拉角转换为四元数。
* @param {Array<number>} out 输出的四元数。
* @param {number} x X轴旋转角度(弧度)。
* @param {number} y Y轴旋转角度(弧度)。
* @param {number} z Z轴旋转角度(弧度)。
* @returns {Array<number>} 输出的四元数。
* @example
* // 将欧拉角(0, 0, 0)转换为四元数
* const out = [];
* _eulerToQuat(out, 0, 0, 0); // 返回 [0, 0, 0, 1]
*/
function _eulerToQuat(out, x, y, z) {
const c1 = Math.cos(x);
const c2 = Math.cos(y);
const c3 = Math.cos(z);
const s1 = Math.sin(x);
const s2 = Math.sin(y);
const s3 = Math.sin(z);
out[0] = s1 * c2 * c3 + c1 * s2 * s3;
out[1] = c1 * s2 * c3 - s1 * c2 * s3;
out[2] = c1 * c2 * s3 + s1 * s2 * c3;
out[3] = c1 * c2 * c3 - s1 * s2 * s3;
return out;
}
/**
* @summary 数学工具类 `THING.Math` 是 `THING.MathUtils` 的别名。
* @class
* @memberof THING
* @alias THING.MathUtils
* @see THING.Math
* @public
**/
class MathUtils {
static DEG2RAD = Math.PI / 180;
static RAD2DEG = 180 / Math.PI;
static TOLERANCE = 0.00001;
static E = Math.E;
static LN2 = Math.LN2;
static LN10 = Math.LN10;
static LOG2E = Math.LOG2E;
static LOG10E = Math.LOG10E;
static PI = Math.PI;
static SQRT1_2 = Math.SQRT1_2;
static SQRT2 = Math.SQRT2;
static abs = Math.abs;
static acos = Math.acos;
static acosh = Math.acosh;
static asin = Math.asin;
static asinh = Math.asinh;
static atan = Math.atan;
static atan2 = Math.atan2;
static atanh = Math.atanh;
static cbrt = Math.cbrt;
static ceil = Math.ceil;
static clz32 = Math.clz32;
static cos = Math.cos;
static cosh = Math.cosh;
static exp = Math.exp;
static expm1 = Math.expm1;
static floor = Math.floor;
static fround = Math.fround;
static hypot = Math.hypot;
static imul = Math.imul;
static log = Math.log;
static log1p = Math.log1p;
static log2 = Math.log2;
static log10 = Math.log10;
static max = Math.max;
static min = Math.min;
static pow = Math.pow;
static random = Math.random;
static round = Math.round;
static sign = Math.sign;
static sin = Math.sin;
static sinh = Math.sinh;
static sqrt = Math.sqrt;
static tan = Math.tan;
static tanh = Math.tanh;
static trunc = Math.trunc;
/**
* vec3是gl-matrix库中的一个类,请参考https://glmatrix.net/docs/module-vec3.html
* @public
*/
static vec3 = vec3;
/**
* quat是gl-matrix库中的一个类,请参考https://glmatrix.net/docs/module-quat.html
* @public
*/
static quat = quat;
/**
* mat3是gl-matrix库中的一个类,请参考https://glmatrix.net/docs/module-mat3.html
* @public
*/
static mat3 = mat3;
/**
* mat4是gl-matrix库中的一个类,请参考https://glmatrix.net/docs/module-mat4.html
* @public
*/
static mat4 = mat4;
/**
* 检查两个数是否相同。
* @param {number} v1 第一个数。
* @param {number} v2 第二个数。
* @param {number} [epsilon=0.001] 误差范围。
* @returns {boolean} 如果两个数在误差范围内相同则返回true,否则返回false。
* @example
* // 检查两个数是否在指定的误差范围内相同
* const v1 = 1.00001;
* const v2 = 1;
* const epsilon = 0.0001;
* const result = MathUtils.equalsNumber(v1, v2, epsilon); // 返回 true
*/
static equalsNumber(v1, v2, epsilon = 0.0001) {
return MathUtils.abs(v1 - v2) < epsilon;
}
/**
* 生成UUID。
* @returns {string} 生成的UUID字符串。
* @example
* // 生成一个随机的UUID
* const uuid = MathUtils.generateUUID(); // 返回一个类似于 "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 的字符串
*/
static generateUUID() {
const d0 = Math.random() * 0xffffffff | 0; // '|0' to make sure convert Float -> Int
const d1 = Math.random() * 0xffffffff | 0;
const d2 = Math.random() * 0xffffffff | 0;
const d3 = Math.random() * 0xffffffff | 0;
const uuid = lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' +
lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' +
lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
return uuid;
}
/**
* 将数字限制在指定范围内。
* @param {number} value 当前值。
* @param {number} min 最小值。
* @param {number} max 最大值。
* @returns {number} 限制在范围内的值。
* @public
* @example
* // 将值限制在0到100之间
* const value = 150;
* const clampedValue = THING.Math.clamp(value, 0, 100); // 返回 100
*/
static clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
/**
* 生成随机布尔值。
* @returns {boolean} 随机生成的布尔值。
* @public
* @example
* // 生成一个随机布尔值
* const randomBool = THING.Math.randomBoolean(); // 返回 true 或 false
*/
static randomBoolean() {
return !!(MathUtils.randomInt() % 2);
}
/**
* 在[min, max]范围内生成随机浮点数。
* @param {number} min 最小值。
* @param {number} max 最大值。
* @returns {number} 生成的随机浮点数。
* @public
* @example
* // 生成0到1之间的随机浮点数
* const random = THING.Math.randomFloat(0, 1); // 例如返回 0.7231...
*/
static randomFloat(min = 0, max = 0xFFFFFFFF) {
return Math.random() * (max - min) + min;
}
/**
* 在[min, max]范围内生成随机整数。
* @param {number} min 最小值。
* @param {number} max 最大值。
* @returns {number} 生成的随机整数。
* @public
* @example
* // 生成1到10之间的随机整数
* const randomInt = THING.Math.randomInt(1, 10); // 返回1到10之间的整数
*/
static randomInt(min = 0, max = 0xFFFFFFFF) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* Get the index from array randomly.
* @param {Array<*>} arr The array.
* @returns {number} The random index of array.
* @example
* // 从数组中随机获取一个索引
* const array = ['apple', 'banana', 'orange', 'grape'];
* const randomIndex = THING.Math.randomIndexFromArray(array); // 返回0到3之间的随机整数
*/
static randomIndexFromArray(arr) {
return MathUtils.randomInt(0, arr.length - 1);
}
/**
* Get the element from array randomly.
* @param {Array<*>} arr The array.
* @returns {*} The random element from array.
* @example
* // 从数组中随机获取一个元素
* const colors = ['red', 'green', 'blue', 'yellow'];
* const randomColor = THING.Math.randomFromArray(colors); // 随机返回一个颜色
*/
static randomFromArray(arr) {
var index = MathUtils.randomIndexFromArray(arr);
return arr[index];
}
/**
* Get the element key from object randomly.
* @param {object} object The object.
* @param {string | Array<string>} excludeKey The exclude key(s) of object.
* @returns {string} The random key from the object.
* @example
* // 从对象中随机获取一个键
* const person = { name: 'John', age: 30, city: 'New York' };
* const randomKey = THING.Math.randomKeyFromObject(person); // 随机返回 'name', 'age' 或 'city'
*/
static randomKeyFromObject(object, excludeKey) {
var keys = Object.keys(object);
if (excludeKey) {
if (Utils.isArray(excludeKey)) {
excludeKey.forEach(key => {
var keyIndex = keys.indexOf(key);
if (keyIndex !== -1) {
keys.splice(keyIndex, 1);
}
});
}
else {
var keyIndex = keys.indexOf(excludeKey);
if (keyIndex !== -1) {
keys.splice(keyIndex, 1);
}
}
}
var index = MathUtils.randomIndexFromArray(keys);
return keys[index];
}
/**
* Get the element from object randomly.
* @param {object} object The object.
* @param {string | Array<string>} excludeKey The exclude key(s) of object.
* @returns {*} The random value from the object.
* @example
* // 从对象中随机获取一个值
* const colors = { red: '#FF0000', green: '#00FF00', blue: '#0000FF' };
* const randomColor = THING.Math.randomFromObject(colors); // 随机返回一个颜色值
*/
static randomFromObject(object, excludeKey) {
var key = MathUtils.randomKeyFromObject(object, excludeKey);
return object[key];
}
/**
* Convert degree to radian.
* @param {number} degree The degree number.
* @returns {number} The converted radian value.
* @example
* // 将90度转换为弧度
* const radians = THING.Math.degToRad(90); // 返回 Math.PI/2 (约1.5708)
*/
static degToRad(degree) {
return degree * MathUtils.DEG2RAD;
}
/**
* Convert radian to degree.
* @param {number} radian The radian number.
* @returns {number} The converted degree value.
* @example
* // 将 π/2 弧度转换为角度
* const degrees = THING.Math.radToDeg(Math.PI/2); // 返回 90
*/
static radToDeg(radian) {
return radian * MathUtils.RAD2DEG;
}
/**
* Convert angles to radians.
* @param {Array<number>} angles The angles array.
* @returns {Array<number>} The converted radians array.
* @example
* // 将多个角度值转换为弧度
* const angles = [0, 90, 180, 270, 360];
* const radians = THING.Math.anglesToRadians(angles); // 返回 [0, π/2, π, 3π/2, 2π]
*/
static anglesToRadians(angles) {
return angles.map(angle => {
return MathUtils.degToRad(angle);
});
}
/**
* Convert radians to angles.
* @param {Array<number>} radians The radians array.
* @returns {Array<number>} The converted angles array.
* @example
* // 将多个弧度值转换为角度
* const radians = [0, Math.PI/4, Math.PI/2, Math.PI, Math.PI*2];
* const angles = THING.Math.radiansToAngles(radians); // 返回 [0, 45, 90, 180, 360]
*/
static radiansToAngles(radians) {
return radians.map(radian => {
return MathUtils.radToDeg(radian);
});
}
/**
* Check whether is prime number.
* @param {number} value The number to check.
* @returns {boolean} True if the number is prime, false otherwise.
* @example
* // 检查一个数是否为质数
* const isPrime2 = THING.Math.isPrime(2); // 返回 true
* const isPrime4 = THING.Math.isPrime(4); // 返回 false
*/
static isPrime(value) {
for (let i = 2, s = Math.sqrt(value); i <= s; i++) {
if (value % i === 0) {
return false;
}
}
return value > 1;
}
/**
* Get the max aligned value.
* @param {number} value The value, it must greater than 0.
* @param {number} alignedValue The value what you want to align, it must greater than 0.
* @returns {number} The max aligned value.
* @example
* let value1 = THING.Math.ceilAlign(10, 12);
* // @expect(value1 == 12);
*/
static ceilAlign(value, alignedValue) {
var temp = value % alignedValue;
if (temp) {
return value + alignedValue - temp;
}
else {
return value;
}
}
/**
* Get the min aligned value.
* @param {number} value The value, it must greater than 0.
* @param {number} alignedValue The value what you want to align, it must greater than 0.
* @returns {number} The min aligned value.
* @example
* let value1 = THING.Math.floorAlign(10, 12);
* // @expect(value1 == 0);
*/
static floorAlign(value, alignedValue) {
return value - (value % alignedValue);
}
/**
* Check whether it's power of 2.
* @param {number} value The number to check.
* @returns {boolean} True if the number is a power of 2, false otherwise.
* @example
* // 检查数字是否为2的幂
* const is2PowerOf2 = THING.Math.isPowerOfTwo(2); // 返回 true
* const is4PowerOf2 = THING.Math.isPowerOfTwo(4); // 返回 true
*/
static isPowerOfTwo(value) {
return (value & (value - 1)) === 0 && value !== 0;
}
/**
* Get the max number for power of 2.
* @param {number} value The input value.
* @returns {number} The smallest power of 2 that is greater than or equal to the input value.
* @example
* // 获取大于等于给定值的最小2的幂
* const ceil3 = THING.Math.ceilPowerOfTwo(3); // 返回 4
* const ceil4 = THING.Math.ceilPowerOfTwo(4); // 返回 4
*/
static ceilPowerOfTwo(value) {
return Math.pow(2, Math.ceil(Math.log(value) / Math.LN2));
}
/**
* Get the min number for power of 2.
* @param {number} value The input value.
* @returns {number} The largest power of 2 that is less than or equal to the input value.
* @example
* // 获取小于等于给定值的最大2的幂
* const floor3 = THING.Math.floorPowerOfTwo(3); // 返回 2
* const floor4 = THING.Math.floorPowerOfTwo(4); // 返回 4
*/
static floorPowerOfTwo(value) {
return Math.pow(2, Math.floor(Math.log(value) / Math.LN2));
}
/**
* Convert number to integer.
* @param {number} value The number to convert.
* @returns {number} The converted integer value.
* @example
* // 将浮点数转换为整数
* const int1 = THING.Math.toInteger(3.7); // 返回 3
* const int2 = THING.Math.toInteger(-2.3); // 返回 -2
*/
static toInteger(value) {
value = +value;
if (value !== value) { // isNaN
value = 0;
}
else if (value !== 0 && value !== (1 / 0) && value !== -(1 / 0)) {
value = (value > 0 || -1) * Math.floor(Math.abs(value));
}
return value;
}
/**
* Get the fraction of number.
* @param {number} x The number to get fraction from.
* @returns {number} The fractional part of the number.
* @example
* // 获取数字的小数部分
* const frac1 = THING.Math.fract(3.7); // 返回 0.7
* const frac2 = THING.Math.fract(-2.3); // 返回 0.7
*/
static fract(x) {
return x - Math.floor(x);
}
/**
* Exchange the element of array.
* @param {Array} arr The array to modify.
* @param {number} index1 The first element's index.
* @param {number} index2 The second element's index.
* @returns {Array} The modified array.
* @example
* // 交换数组中的两个元素
* const array = [1, 2, 3, 4, 5];
* THING.Math.swapArray(array, 1, 3); // 返回 [1, 4, 3, 2, 5]
*/
static swapArray(arr, index1, index2) {
arr[index1] = arr.splice(index2, 1, arr[index1])[0];
return arr;
}
/**
* Round up the number.
* @param {number} value The value to round.
* @param {number} length The length of fraction.
* @returns {number} The rounded value.
* @example
* // 将数字四舍五入到指定小数位
* const rounded1 = THING.Math.roundUp(3.14159, 2); // 返回 3.14
* const rounded2 = THING.Math.roundUp(3.14159, 3); // 返回 3.142
*/
static roundUp(value, length) {
if (typeof (value) == 'number') {
var t = 1;
for (; length > 0; t *= 10, length--);
for (; length < 0; t /= 10, length++);
return Math.round(value * t) / t;
}
else {
return value;
}
}
/**
* Convert number to hex string format.
* @param {number} value The number to convert.
* @param {string} [pattern="00000000"] The pattern, default is 32 bits mode.
* @returns {string} The hex string representation.
* @example
* // 将数字转换为十六进制字符串
* const hex1 = THING.Math.toHexNumberString(255); // 返回 "000000ff"
* const hex2 = THING.Math.toHexNumberString(65535, "0000"); // 返回 "ffff"
*/
static toHexNumberString(value, pattern = "00000000") {
var sign = value < 0 ? true : false;
value = value.toString(16);
if (sign) {
value = value.substring(1);
}
var hexString = pattern + value;
hexString = hexString.substring(value.length, hexString.length);
if (sign) {
hexString = '-' + hexString;
}
return hexString;
}
/**
* Get the distance between vectors (squared).
* @param {Array<number>} v1 The first vector.
* @param {Array<number>} v2 The second vector.
* @returns {number} The squared distance between the vectors.
* @example
* // 计算2D向量之间的平方距离
* const point1 = [1, 2];
* const point2 = [4, 6];
* const distSquared2D = THING.Math.getDistanceToSquared(point1, point2); // 返回 25 (5²)
*/
static getDistanceToSquared(v1, v2) {
if (v1.length == 2 && v2.length == 2) {
var dx = v1[0] - v2[0], dy = v1[1] - v2[1];
return dx * dx + dy * dy;
}
else {
var dx = v1[0] - v2[0], dy = v1[1] - v2[1], dz = v1[2] - v2[2];
return dx * dx + dy * dy + dz * dz;
}
}
/**
* 获取两个向量之间的距离。
* @param {Array<number>} v1 第一个向量。
* @param {Array<number>} v2 第二个向量。
* @returns {number} 两个向量之间的欧几里得距离。
* @public
* @example
* // 计算2D点之间的距离
* const point1 = [0, 0];
* const point2 = [3, 4];
* const distance2D = THING.Math.getDistance(point1, point2); // 返回 5
*/
static getDistance(v1, v2) {
return Math.sqrt(MathUtils.getDistanceToSquared(v1, v2));
}
/**
* get duration by speed
* @param {Array<Array<number>>} path The path.
* @param {number} speed The speed. (m/s)
* @param {boolean} [closure=false] True indicates it's closure lines. default is false.
* @returns {number} The duration. (ms)
* @example
* let box = new THING.Box();
* let path = [[0, 0, 0], [10, 0, 0]]
* box.movePath({
* path,
* duration: THING.MathUtils.getDurationFromSpeed(path, 4)
* })
*/
static getDurationFromSpeed(path, speed, closure = false) {
if (!path || !speed || speed <= 0) {
return 0;
}
let totalDistance = MathUtils.getDistanceFromPoints(path, closure);
return totalDistance / speed * 1000;
}
/**
* 在[min, max]范围内生成随机向量。
* @param {Array<number>} min 最小向量。
* @param {Array<number>} max 最大向量。
* @returns {Array<number>} The generated random vector.
* @public
* @example
* // 生成2D随机向量
* const min2D = [0, 0];
* const max2D = [10, 5];
* const random2D = THING.Math.randomVector(min2D, max2D); // 返回如 [3.7, 2.1] 的随机向量
*
* // 生成3D随机向量
* const min3D = [-5, 0, -10];
* const max3D = [5, 10, 10];
* const random3D = THING.Math.randomVector(min3D, max3D); // 返回如 [2.4, 7.8, -3.2] 的随机向量
*
* // 在游戏中生成随机位置
* const minBounds = [0, 0, 0];
* const maxBounds = [100, 0, 100]; // 平面上的随机位置
* const randomPosition = THING.Math.randomVector(minBounds, maxBounds);
*/
static randomVector(min, max) {
if (min.length == 2) {
return [
MathUtils.randomFloat(min[0], max[0]),
MathUtils.randomFloat(min[1], max[1]),
];
}
else if (min.length == 3) {
return [
MathUtils.randomFloat(min[0], max[0]),
MathUtils.randomFloat(min[1], max[1]),
MathUtils.randomFloat(min[2], max[2]),
];
}
return null;
}
/**
* 在[min, max]范围内生成随机向量 [x, y]。
* @param {number} min 最小值。
* @param {number} max 最大值。
* @returns {Array<number>} 生成的2D随机向量。
* @public
* @example
* // 生成[0, 10]范围内的随机2D向量
* const random2D = THING.Math.randomVector2Range(0, 10); // 返回如 [3.7, 8.2] 的随机向量
*/
static randomVector2Range(min, max) {
return MathUtils.randomVector([min, min], [max, max]);
}
/**
* Generate random vector [x, y, z] in [min, max] range.
* @param {number} min The minimum value.
* @param {number} max The maximum value.
* @returns {Array<number>} The generated 3D random vector.
* @example
* // 生成[0, 10]范围内的随机3D向量
* const random3D = THING.Math.randomVector3Range(0, 10); // 返回如 [3.7, 8.2, 1.5] 的随机向量
*/
static randomVector3Range(min, max) {
return MathUtils.randomVector([min, min, min], [max, max, max]);
}
/**
* Generate random color.
* @returns {number} The random color value in hexadecimal format.
* @example
* // 生成随机颜色值
* const randomColor = THING.Math.randomColor(); // 返回如 0x7A2F9C 的随机颜色值
*/
static randomColor() {
return MathUtils.randomInt(0, 0xFFFFFF);
}
/**
* Lerp number.
* @param {number} start The start number.
* @param {number} end The end number.
* @param {number} alpha The interpolation factor between [0, 1].
* @returns {number} The interpolated value.
* @example
* // 在两个数值之间进行线性插值
* const start = 0;
* const end = 10;
* const halfWay = THING.Math.lerp(start, end, 0.5); // 返回 5
*/
static lerp(start, end, alpha) {
return start + (end - start) * alpha;
}
/**
* Lerp vector.
* @param {Array<number>} start The start vector.
* @param {Array<number>} end The end vector.
* @param {number} alpha The interpolation factor between [0, 1].
* @param {Array<number>} [result] Optional array to store the result.
* @returns {Array<number>} The interpolated vector.
* @example
* // 在两个3D向量之间进行线性插值
* const startPos = [0, 0, 0];
* const endPos = [10, 5, 20];
* const halfWayPos = THING.Math.lerpVector(startPos, endPos, 0.5); // 返回 [5, 2.5, 10]
*/
static lerpVector(start, end, alpha, result) {
result = result || [];
result[0] = start[0] + (end[0] - start[0]) * alpha;
result[1] = start[1] + (end[1] - start[1]) * alpha;
result[2] = start[2] + (end[2] - start[2]) * alpha;
return result;
}
/**
* Lerp path.
* @param {Array<Array<number>>} path The path points array.
* @param {number} alpha The interpolation factor between [0, 1].
* @param {Function} easingFunction The easing function for interpolation.
* @returns {Array<number>} The interpolated position on the path.
* @example
* let path = [[0,0,0], [10,0,0], [10,0,10]];
* let alpha = 0.2;
* THING.MathUtils.lerpPath(path, alpha, THING.LerpType.Quadratic.In);
*/
static lerpPath(path, alpha, easingFunction = (value) => { return value; }) {
let result = [path[0][0], path[0][1], path[0][2]];
if (alpha === 0) {
return path[0];
}
if (alpha >= 1) {
return path[path.length - 1];
}
// Calculate the processed ratio
const processedAlpha = easingFunction(alpha);
// Calculate total distance length
let totalPathLength = MathUtils.getDistanceFromPoints(path);
// Confirm the position
let targetDistance = processedAlpha * totalPathLength;
let currentDistance = 0;
let currentIndex = 0;
while (currentIndex < path.length - 1) {
let segmentLength = MathUtils.getDistance(path[currentIndex + 1], path[currentIndex]);
if (currentDistance + segmentLength < targetDistance) {
currentDistance += segmentLength;
currentIndex++;
}
else {
let t = (targetDistance - currentDistance) / segmentLength;
result[0] = path[currentIndex][0] + t * (path[currentIndex + 1][0] - path[currentIndex][0]);
result[1] = path[currentIndex][1] + t * (path[currentIndex + 1][1] - path[currentIndex][1]);
result[2] = path[currentIndex][2] + t * (path[currentIndex + 1][2] - path[currentIndex][2]);
break;
}
}
return result;
}
/**
* Get the ratio of each stage of the path.
* @param {Array<Array<number>>} path The path points array.
* @returns {Array<number>} Array of ratios for each path segment.
* @example
* let pathRatios = THING.Math.getPathRatios([[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10], [0,0,0]]);
* console.log(pathRatios); // [0, 0.25, 0.5, 0.75, 1]
*/
static getPathRatios(path) {
let distances = [];
let totalPathLength = 0;
for (let i = 1; i < path.length; i++) {
let segmentLength = Math.sqrt(
Math.pow(path[i][0] - path[i - 1][0], 2) +
Math.pow(path[i][1] - path[i - 1][1], 2) +
Math.pow(path[i][2] - path[i - 1][2], 2)
);
totalPathLength += segmentLength;
distances.push(totalPathLength);
}
if (totalPathLength === 0) {
return [];
}
var pointRatios = [0];
for (var i = 0; i < distances.length; i++) {
var ratio = distances[i] / totalPathLength;
pointRatios.push(ratio);
}
return pointRatios;
}
/**
* 检查向量是否相同。
* @param {Array<number>} v1 第一个向量。
* @param {Array<number>} v2 第二个向量。
* @param {number} [epsilon=0.001] 误差范围。
* @returns {boolean} 如果向量在误差范围内相同则返回true,否则返回false。
* @public
* @example
* // 检查2D向量是否相等
* const vec1 = [1, 2];
* const vec2 = [1, 2];
* const areEqual2D = THING.Math.equalsVector(vec1, vec2); // 返回 true
*/
static equalsVector(v1, v2, epsilon = 0.0001) {
if (v1.length === 2) {
if (!MathUtils.equalsNumber(v1[0], v2[0], epsilon)
|| !MathUtils.equalsNumber(v1[1], v2[1], epsilon)) {
return false;
}
}
else if (v1.length === 3) {
if (!MathUtils.equalsNumber(v1[0], v2[0], epsilon)
|| !MathUtils.equalsNumber(v1[1], v2[1], epsilon)
|| !MathUtils.equalsNumber(v1[2], v2[2], epsilon)) {
return false;
}
}
return true;
}
/**
* Check whether [XYZ] vectors is the same.
* @param {Array<number>} v1 The first vector.
* @param {Array<number>} v2 The second vector.
* @param {number} [epsilon=0.001] The epsilon range for comparison.
* @returns {boolean} True if vectors are equal within epsilon range.
* @example
* // 检查3D向量是否相等(带误差容忍)
* const pos1 = [10, 5, 7];
* const pos2 = [10.0001, 4.9999, 7.0001];
* const areEqual = THING.Math.equalsVector3(pos1, pos2); // 返回 true
*/
static equalsVector3(v1, v2, epsilon = 0.0001) {
if (!v1 || !v2) {
return false;
}
if (!MathUtils.equalsNumber(v1[0], v2[0], epsilon)
|| !MathUtils.equalsNumber(v1[1], v2[1], epsilon)
|| !MathUtils.equalsNumber(v1[2], v2[2], epsilon)) {
return false;
}
return true;
}
/**
* Check whether [XYZ] vectors is the same in exact mode.
* @param {Array<number>} v1 The first vector.
* @param {Array<number>} v2 The second vector.
* @returns {boolean} True if vectors are exactly equal.
* @example
* // 检查3D向量是否完全相等(不允许误差)
* const pos1 = [10, 5, 7];
* const pos2 = [10, 5, 7];
* const areExactlyEqual = THING.Math.exactEqualsVector3(pos1, pos2); // 返回 true
*/
static exactEqualsVector3(v1, v2) {
if (v1[0] != v2[0]
|| v1[1] != v2[1]
|| v1[2] != v2[2]) {
return false;
}
return true;
}
/**
* Check whether it's zero vector.
* @param {Array<number>} v The vector to check.
* @returns {boolean} True if the vector is zero vector.
* @example
* // 检查2D向量是否为零向量
* const zeroVec2D = [0, 0];
* const isZero2D = THING.Math.isZeroVector(zeroVec2D); // 返回 true
*/
static isZeroVector(v) {
if (v) {
for (var i = 0; i < v.length; i++) {
if (v[i]) {
return false;
}
}
}
return true;
}
/**
* Check whether [XYZ] vectors is the Parallel.
* @param {Array<number>} v1 The first vector.
* @param {Array<number>} v2 The second vector.
* @returns {boolean} True if vectors are parallel.
* @example
* // 检查两个向量是否平行
* const vec1 = [1, 2, 3];
* const vec2 = [2, 4, 6]; // vec1 的 2 倍
* const areParallel = THING.Math.isParallelVector(vec1, vec2); // 返回 true
*/
static isParallelVector(v1, v2) {
if (v1[0] * v2[1] === v1[1] * v2[0] && v1[0] * v2[2] === v1[2] * v2[0] && v1[1] * v2[2] === v1[2] * v2[1]) {
return true;
}
return false;
}
/**
* 向量相加。
* @param {Array<number>} v1 第一个向量。
* @param {Array<number>} v2 第二个向量或标量。
* @param {Array<number>} [result] 可选的结果数组。
* @returns {Array<number>} 相加后的向量。
* @public
* @example
* // 两个3D向量相加
* const vec1 = [1, 2, 3];
* const vec2 = [4, 5, 6];
* const sum = THING.Math.addVector(vec1, vec2); // 返回 [5, 7, 9]
*/
static addVector(v1, v2, result) {
result = result || [];
if (Utils.isNumber(v2)) {
result[0] = v1[0] + v2;
result[1] = v1[1] + v2;
result[2] = v1[2] + v2;
}
else {
result[0] = v1[0] + v2[0];
result[1] = v1[1] + v2[1];
result[2] = v1[2] + v2[2];
}
return result;
}
/**
* 向量相减。
* @param {Array<number>} v1 第一个向量。
* @param {Array<number>} v2 第二个向量或标量。
* @param {Array<number>} [result] 可选的结果数组。
* @returns {Array<number>} 相减后的向量。
* @public
* @example
* // 两个3D向量相减
* const vec1 = [5, 7, 9];
* const vec2 = [1, 2, 3];
* const diff = THING.Math.subVector(vec1, vec2); // 返回 [4, 5, 6]
*/
static subVector(v1, v2, result) {
result = result || [];
if (Utils.isNumber(v2)) {
result[0] = v1[0] - v2;
result[1] = v1[1] - v2;
result[2] = v1[2] - v2;
}
else {
result[0] = v1[0] - v2[0];
result[1] = v1[1] - v2[1];
result[2] = v1[2] - v2[2];
}
return result;
}
/**
* 缩放向量。
* @param {Array<number>} v 要缩放的向量。
* @param {Array<number>|number} scale 缩放因子或向量。
* @param {Array<number>} [result] 可选的结果数组。
* @returns {Array<number>} 缩放后的向量。
* @public
* @example
* // 向量乘以标量
* const vec = [1, 2, 3];
* const scalar = 2;
* const scaled = THING.Math.scaleVector(vec, scalar); // 返回 [2, 4, 6]
*/
static scaleVector(v, scale, result) {
result = result || [];
if (Utils.isNumber(scale)) {
result[0] = v[0] * scale;
result[1] = v[1] * scale;
result[2] = v[2] * scale;
}
else {
result[0] = v[0] * scale[0];
result[1] = v[1] * scale[1];
result[2] = v[2] * scale[2];
}
return result;
}
/**
* 向量相除。
* @param {Array<number>} v 被除向量。
* @param {Array<number>|number} scale 除数向量或标量。
* @param {Array<number>} [result] 可选的结果数组。
* @returns {Array<number>} 相除后的向量。
* @public
* @example
* // 向量除以标量
* const vec = [10, 20, 30];
* const scalar = 2;
* const divided = THING.Math.divideVector(vec, scalar); // 返回 [5, 10, 15]
*
* // 向量与向量相除(逐元素除法)
* const v1 = [10, 20, 30];
* const v2 = [2, 4, 5];
* const divided2 = THING.Math.divideVector(v1, v2); // 返回 [5, 5, 6]
*/
static divideVector(v, scale, result) {
result = result || [];
if (Utils.isNumber(scale)) {
result[0] = v[0] / scale;
result[1] = v[1] / scale;
result[2] = v[2] / scale;
}
else {
result[0] = v[0] / scale[0];
result[1] = v[1] / scale[1];
result[2] = v[2] / scale[2];
}
return result;
}
/**
* Clamp vector.
* @param {Array<number>} v The vector to clamp.
* @param {Array<number>} min The minimum vector.
* @param {Array<number>} max The maximum vector.
* @param {Array<number>} [result] Optional array to store the result.
* @returns {Array<number>} The clamped vector.
* @example
* // 将向量限制在指定范围内
* const vec = [15, -5, 30];
* const min = [0, 0, 0];
* const max = [10, 10, 10];
* const clamped = THING.Math.clampVector(vec, min, max); // 返回 [10, 0, 10]
*/
static clampVector(v, min, max, result) {
result = result || [];
result[0] = Math.max(min[0], Math.min(max[0], v[0]));
result[1] = Math.max(min[1], Math.min(max[1], v[1]));
result[2] = Math.max(min[2], Math.min(max[2], v[2]));
return result;
}
/**
* 获取向量的点积。
* @param {Array<number>} v1 第一个向量。
* @param {Array<number>} v2 第二个向量。
* @returns {number} 两个向量的点积。
* @public
* @example
* // 计算两个向量的点积
* const v1 = [1, 0, 0];
* const v2 = [0, 1, 0];
* const dot1 = THING.Math.dotVector(v1, v2); // 返回 0,因为向量垂直
*/
static dotVector(v1, v2) {
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
/**
* 获取向量的叉积。
* @param {Array<number>} v1 第一个向量。
* @param {Array<number>} v2 第二个向量。
* @param {Array<number>} [result] 可选的结果数组。
* @returns {Array<number>} 两个向量的叉积。
* @public
* @example
* // 计算两个向量的叉积
* const v1 = [1, 0, 0]; // x轴单位向量
* const v2 = [0, 1, 0]; // y轴单位向量
* const cross = THING.Math.crossVector(v1, v2); // 返回 [0, 0, 1],即z轴单位向量
*/
static crossVector(v1, v2, result) {
result = result || [];
result[0] = v1[1] * v2[2] - v1[2] * v2[1];
result[1] = v1[2] * v2[0] - v1[0] * v2[2];
result[2] = v1[0] * v2[1] - v1[1] * v2[0];
return result;
}
/**
* 获取负向量。
* @param {Array<number>} v 输入向量。
* @param {Array<number>} [result] 可选的结果数组。
* @returns {Array<number>} 输入向量的负向量。
* @public
* @example
* // 获取向量的负向量
* const vec = [1, 2, 3];
* const negated = THING.Math.negVector(vec); // 返回 [-1, -2, -3]
*/
static negVector(v, result) {
result = result || [];
result[0] = -v[0];
result[1] = -v[1];
result[2] = -v[2];
return result;
}
/**
* 获取归一化向量。
* @param {Array<number>} v 要归一化的向量。
* @param {Array<number>} [target=[0,0,0]] 可选的目标数组。
* @returns {Array<number>} 归一化后的向量。
* @public
* @example
* // 将向量归一化(单位化)
* const vec = [3, 0, 0];
* const normalized = THING.Math.normalizeVector(vec); // 返回 [1, 0, 0]
*/
static normalizeVector(v, target = [0, 0, 0]) {
return vec3.normalize(target, v);
}
/**
* Get the min vector.
* @param {Array<Array<number>>} points Array of points.
* @returns {Array<number>} The minimum vector containing the smallest values for each component.
* @example
* // 获取多个点的最小坐标向量
* const points = [
* [1, 5, 3],
* [2, 1, 7],
* [0, 3, 2]
* ];
* const minPoint = THING.Math.minVector(points); // 返回 [0, 1, 2]
*/
static minVector(points) {
if (!points.length) {
return null;
}
if (points.length === 1) {
return points[0].slice(0);
}
var minPoint = points[0].slice(0);
for (var i = 1; i < points.length; i++) {
var point = points[i];
minPoint[0] = Math.min(minPoint[0], point[0]);
minPoint[1] = Math.min(minPoint[1], point[1]);
minPoint[2] = Math.min(minPoint[2], point[2]);
}
return minPoint;
}
/**
* Get the max vector.
* @param {Array<Array<number>>} points Array of points.
* @returns {Array<number>} The maximum vector containing the largest values for each component.
* @example
* // 获取多个三维点中每个维度的最大值
* const points = [
* [1, 2, 3],
* [4, 0, 2],
* [2, 5, 1]
* ];
* const maxPoint = THING.Math.maxVector(points); // 返回 [4, 5, 3]
*/
static maxVector(points) {
if (!points.length) {
return null;
}
if (points.length === 1) {
return points[0].slice(0);
}
var maxPoint = points[0].slice(0);
for (var i = 1; i < points.length; i++) {
var point = points[i];
maxPoint[0] = Math.max(maxPoint[0], point[0]);
maxPoint[1] = Math.max(maxPoint[1], point[1]);
maxPoint[2] = Math.max(maxPoint[2], point[2]);
}
return maxPoint;
}
/**
* 绕指定轴旋转向量。
* @param {Array<number>} vector 要旋转的向量。
* @param {Array<number>} axis 旋转轴。
* @param {number} angle 旋转角度(度)。
* @param {Array<number>} [result] 可选的结果数组。
* @returns {Array<number>} 旋转后的向量。
* @public
* @example
* // 绕Y轴旋转一个向量90度
* const vector = [1, 0, 0]; // X轴正方向的向量
* const axis = [0, 1, 0]; // Y轴作为旋转轴
* const angle = 90; // 旋转90度
* const result = THING.Math.rotateVector(vector, axis, angle); // 返回 [0, 0, -1]
*/
static rotateVector(vector, axis, angle, result) {
result = result || [];
// Create rotation quaternion
const rotQuat = MathUtils.getQuatFromAxisAngle(axis, angle);
// Apply rotation to vector
return MathUtils.vec3ApplyQuat(vector, rotQuat, result);
}
/**
* Get vector3 from matrix column.
* @param {Array<number>} matrix The source matrix.
* @param {number} index The index of column.
* @param {Array<number>} [result] Optional array to store the result.
* @returns {Array<number>} The extracted vector from matrix column.
* @example
* // 从变换矩阵中提取X轴方向向量
* const matrix = [
* 1, 0, 0, 0,
* 0, 1, 0, 0,
* 0, 0, 1, 0,
* 0, 0, 0, 1
* ]; // 单位矩阵
* const xAxis = THING.Math.getVec3FromMatrixColumn(matrix, 0); // 返回 [1, 0, 0]
*/
static getVec3FromMatrixColumn(matrix, index, result) {
result = result || [];
var offset = index * 4;
result[0] = matrix[offset];
result[1] = matrix[offset + 1];
result[2] = matrix[offset + 2];
return result;
}
/**
* Transform direction vector by matrix.
* @param {Array<number>} vector The direction vector to transform.
* @param {Array<number>} matrix The transformation matrix.
* @param {Array<number>} result The array to store the result.
* @returns {Array<number>} The transformed direction vector.
* @example
* // 使用变换矩阵转换方向向量
* const direction = [0, 0, 1]; // Z轴正方向
* const matrix = [
* 0, 0, 1, 0, // 第一列
* 0, 1, 0, 0, // 第二列
* -1, 0, 0, 0, // 第三列
* 0, 0, 0, 1 // 第四列
* ]; // 这个矩阵表示绕Y轴旋转90度
* const result = [0, 0, 0];
* THING.Math.transformDirection(direction, matrix, result); // result为 [-1, 0, 0]
*/
static transformDirection(vector, matrix, result = [0, 0, 0]) {
const x = vector[0], y = vector[1], z = vector[2];
const e = matrix;
result[0] = e[0] * x + e[4] * y + e[8] * z;
result[1] = e[1] * x + e[5] * y + e[9] * z;
result[2] = e[2] * x + e[6] * y + e[10] * z;
return MathUtils.normalizeVector(result, result);
}
/**
* 获取向量的长度。
* @param {Array<number>} v 输入向量。
* @returns {number} 向量的长度。
* @public
* @example
* // 计算向量的长度
* const vec = [3, 4, 0];
* const length = THING.Math.getVectorLength(vec); // 返回 5
*/
static getVectorLength(v) {
return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
/**
* Get the length of vector (do not use Math.sqrt()).
* @param {Array<number>} v The vector.
* @returns {number} The squared length of the vector.
* @example
* // 计算向量的平方长度
* const vec = [3, 4, 0];
* const lengthSquared = THING.Math.getVectorLengthSquared(vec); // 返回 25
*/
static getVectorLengthSquared(v) {
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
}
/**
* Get the direction vector.
* @param {Array<number>} v1 The start vector.
* @param {Array<number>} v2 The end vector.
* @returns {Array<number>} The normalized direction vector from v1 to v2.
* @example
* // 计算从一点到另一点的单位方向向量
* const startPoint = [0, 0, 0];
* const endPoint = [3, 4, 0];
* const direction = THING.Math.getDirection(startPoint, endPoint); // 返回 [0.6, 0.8, 0]
*/
static getDirection(v1, v2) {
var dir = MathUtils.subVector(v2, v1);
return MathUtils.normalizeVector(dir);
}
/**
* 从点集中获取距离。
* @param {Array<Array<number>>} points 点集。
* @param {boolean} [closure=false] 是否闭合线段。
* @returns {number} 点集中所有相邻点之间的距离之和。
* @public
* @example
* // 计算路径的总长度
* const path = [
* [0, 0, 0],
* [10, 0, 0],
* [10, 10, 0],
* [0, 10, 0]
* ];
* const pathLength = THING.Math.getDistanceFromPoints(path); // 返回 30
*/
static getDistanceFromPoints(points, closure = false) {
if (!points || points.length < 2) {
return 0;
}
var distance = 0;
for (var i = 0; i < points.length - 1; i++) {
distance += MathUtils.getDistance(points[i], points[i + 1]);
}
if (closure) {
distance += MathUtils.getDistance(points[points.length - 1], points[0]);
}
return distance;
}
/**
* 从点集中获取中心点。
* @param {Array<Array<number>>} points 点集。
* @returns {Array<number>} 所有点的几何中心。
* @public
* @example
* // 计算多个点的中心点
* const points = [
* [0, 0, 0],
* [10, 0, 0],
* [10, 10, 0],
* [0, 10, 0]
* ];
* const center = THING.Math.getCenterFromPoints(points); // 返回 [5, 5, 0]
*/
static getCenterFromPoints(points) {
if (!points) {
return [0, 0, 0];
}
if (!points.length) {
return [0, 0, 0];
}
var center = [0, 0, 0];
points.forEach(point => {
center[0] += point[0];
center[1] += point[1];
center[2] += point[2];
});
center[0] /= points.length;
center[1] /= points.length;
center[2] /= points.length;
return center;
}
/**
* 创建新的点集,删除重复的点。
* @param {Array<Array<number>>} points 原始点集。
* @param {number} [epsilon=0.001] 判断点重复的误差范围。
* @returns {Array<Array<number>>} 去除重复点后的点集。
* @public
* @example
* // 移除数组中的重复点
* const points = [
* [1, 2, 3],
* [1.0001, 2, 3], // 非常接近第一个点
* [4, 5, 6],
* [7, 8, 9],
* [4.0002, 5.0001, 6] // 非常接近第三个点
* ];
* const uniquePoints = THING.Math.toUniquePoints(points); // 返回 [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
*/
static toUniquePoints(points, epsilon = 0.0001) {
var results = [];
points.forEach(point => {
for (var i = 0; i < results.length; i++) {
var result = results[i];
if (MathUtils.equalsVector(result, point, epsilon)) {
return;
}
}
results.push(point);
});
return results;
}
/**
* 获取两个向量之间的夹角。
* @param {Array<number>} v1 第一个向量。
* @param {Array<number>} v2 第二个向量。
* @returns {number} 两个向量之间的夹角(度)。
* @public
* @example
* // 计算2D向量之间的角度
* const vector1 = [1, 0]; // 指向x轴正方向的向量
* const vector2 = [0, 1]; // 指向y轴正方向的向量
* const angle2D = THING.Math.getAngleBetweenVectors(vector1, vector2); // 返回 90 (度)
*/
static getAngleBetweenVectors(v1, v2) {
if (v1.length === 2) {
var dot = v1[0] * v2[0] + v1[1] * v2[1];
var det = v1[0] * v2[1] - v1[1] * v2[0];
var radian = Math.atan2(det, dot);
return MathUtils.radToDeg(radian);
}
else if (v1.length === 3) {
v1 = MathUtils.normalizeVector(v1);
v2 = MathUtils.normalizeVector(v2);
var dot = MathUtils.dotVector(v1, v2);
// force dot into acceptable range.
if (dot > 1 && dot < 1 + MathUtils.TOLERANCE) {
dot = 1;
}
else if (dot < -1 && dot > -1 - MathUtils.TOLERANCE) {
dot = -1;
}
return MathUtils.radToDeg(MathUtils.acos(dot));
}
return 0;
}
/**
* Get area by points in plane(2D).
* @param {Array<Array<number>>} points The points array defining the polygon.
* @returns {number} The area of the polygon (positive if points are counter-clockwise, negative if clockwise).
* @example
* // 计算三角形面积
* const triangle = [
* [0, 0],
* [10, 0],
* [5, 8]
* ];
* const triangleArea = THING.Math.getArea(triangle); // 返回 40
*/
static getArea(points) {
var n = points.length;
var a = 0.0;
for (var p = n - 1, q = 0; q < n; p = q++) {
a += points[p][0] * points[q][1] - points[q][0] * points[p][1];
}
return a * 0.5;
}
/**
* Check whether it's clockwise.
* @param {Array<Array<number>>} points The points array defining the polygon.
* @returns {boolean} True if the points are arranged in clockwise order.
* @example
* // 检查点是否按顺时针排列
* const clockwisePoints = [
* [0, 0],
* [0, 5],
* [5, 5],
* [5, 0]
* ];
* const isClockwise = THING.Math.isClockWise(clockwisePoints); // 返回 true
*/
static isClockWise(points) {
return MathUtils.getArea(points) < 0;
}
/**
* Make points as clockwise points.
* @param {Array<Array<number>>} points The input points array.
* @returns {Array<Array<number>>} The points array arranged in clockwise order.
* @example
* // 将逆时针排列的点转换为顺时针排列
* const counterClockwisePoints = [
* [0, 0],
* [5, 0],
* [5, 5],
* [0, 5]
* ];
* const clockwisePoints = THING.Math.makeClockWisePoints(counterClockwisePoints);
* // 返回顺时针排列的点: [[0, 0], [0, 5], [5, 5], [5, 0]]
*/
static makeClockWisePoints(points) {
// Thanks for sharing code: https://stackoverflow.com/questions/45660743/sort-points-in-counter-clockwise-in-javascript
var _points = points.slice(0);
// Find min max to get center
// Sort from top to bottom
_points.sort((a, b) => a[1] - b[1]);
// Get center y
const cy = (_points[0][1] + _points[_points.length - 1][1]) / 2;
// Sort from right to left
_points.sort((a, b) => b[0] - a[0]);
// Get center x
const cx = (_points[0][0] + _points[_points.length - 1][0]) / 2;
// Center point
var center = [cx, cy];
// Pre calculate the angles as it will be slow in the sort
// As the points are sorted from right to left the first point
// is the rightmost
// Starting angle used to reference other angles
var startAng;
_points.forEach(point => {
var ang = Math.atan2(point[1] - center[1], point[0] - center[0]);
if (!startAng) {
startAng = ang
}
else {
if (ang < startAng) { // ensure that all points are clockwise of the start point
ang += Math.PI * 2;
}
}
point.angle = ang; // add the angle to the point
});
// first sort clockwise
_points.sort((a, b) => a.angle - b.angle);
// then reverse the order
const ccwPoints = _points.reverse();
// move the last point back to the start
ccwPoints.unshift(ccwPoints.pop());
return ccwPoints.map(point => {
return [point[0], point[1]];
});
}
/**
* 检查点是否在矩形区域内。
* @param {Array<number>} point 要检查的点 [x, y]。
* @param {Array<number>} region 矩形区域 [minX, minY, maxX, maxY]。
* @returns {boolean} 如果点在区域内返回true,否则返回false。
* @example
* // 检查点是否在矩形区域内
* const point = [5, 5];
* const region = [0, 0, 10, 10]; // [minX, minY, maxX, maxY]
* const isInside = THING.Math.intersectPoint(point, region); // 返回 true
*
* // 检查点是否在区域外
* const outsidePoint = [15, 5];
* const isOutside = THING.Math.intersectPoint(outsidePoint, region); // 返回 false
*
* // 检查边界点
* const boundaryPoint = [10, 10]; // 在右上角边界上
* const isBoundary = THING.Math.intersectPoint(boundaryPoint, region); // 返回 true
*
* // 在地图或UI中使用
* const mousePosition = [120, 150];
* const buttonRegion = [100, 100, 200, 200];
* if (THING.Math.intersectPoint(mousePosition, buttonRegion)) {
* console.log('鼠标在按钮区域内');
* }
*/
static intersectPoint(point, region) {
var x = point[0];
var y = point[1];
if (x < region[0]) {
return false;
}
if (x > region[2]) {
return false;
}
if (y < region[1]) {
return false;
}
if (y > region[3]) {
return false;
}
return true;
}
/**
* Get the offset from phi(horzAngle) and theta(vertAngle).
* @param {number} horzAngle The horz(phi) angle, 0 indicates z+ axix.
* @param {number} vertAngle The vert(theta) angle, 90 indicates y+ axix.
* @returns {Array<number>} The direction vector as an array of three numbers representing the x, y, and z coordinates.
* @example
* // 获取向前的方向向量(z轴正方向)
* const forwardDirection = THING.Math.getDirectionFromAngles(0, 0); // 返回 [0, 0, 1]
*
* // 获取向上的方向向量(y轴正方向)
* const upDirection = THING.Math.getDirectionFromAngles(0, 90); // 返回 [0, 1, 0]
*
* // 获取向右的方向向量(x轴正方向)
* const rightDirection = THING.Math.getDirectionFromAngles(90, 0); // 返回 [1, 0, 0]
*
* // 在相机控制中使用
* const cameraHorzAngle = 45; // 水平旋转45度
* const cameraVertAngle = 30; // 垂直仰角30度
* const lookDirection = THING.Math.getDirectionFromAngles(cameraHorzAngle, cameraVertAngle);
* // 返回相机朝向的单位向量
* @public
*/
static getDirectionFromAngles(horzAngle, vertAngle) {
var theta = vertAngle / 180 * Math.PI + Math.PI / 2;
var phi = -horzAngle / 180 * Math.PI + Math.PI / 2;
var r = Math.sin(theta);
return [
r * Math.cos(phi),
-Math.cos(theta),
r * Math.sin(phi)
];
}
/**
* Get the offset position by angles.
* @param {number} horzAngle The horz(phi) angle.
* @param {number} vertAngle The vert(theta) angle.
* @param {number} distance The distance.
* @returns {Array<number>} The offset position as an array of three numbers representing the x, y, and z coordinates.
* @example
* // 计算前方10单位的位置
* const forwardOffset = THING.Math.getOffsetFromAngles(0, 0, 10); // 返回 [0, 0, 10]
*
* // 计算上方5单位的位置
* const upOffset = THING.Math.getOffsetFromAngles(0, 90, 5); // 返回 [0, 5, 0]
*
* // 计算右侧8单位的位置
* const rightOffset = THING.Math.getOffsetFromAngles(90, 0, 8); // 返回 [8, 0, 0]
*
* // 在相机定位中使用
* const cameraPosition = [0, 0, 0];
* const cameraHorzAngle = 45; // 水平旋转45度
* const cameraVertAngle = 30; // 垂直仰角30度
* const targetDistance = 100; // 目标距离
* const targetOffset = THING.Math.getOffsetFromAngles(cameraHorzAngle, cameraVertAngle, targetDistance);
* // 计算目标位置 = 相机位置 + 偏移量
* const targetPosition = [
* cameraPosition[0] + targetOffset[0],
* cameraPosition[1] + targetOffset[1],
* cameraPosition[2] + targetOffset[2]
* ];
*/
static getOffsetFromAngles(horzAngle, vertAngle, distance) {
var direction = MathUtils.getDirectionFromAngles(horzAngle, vertAngle);
return vec3.scale(direction, direction, distance);
}
/**
* Get the horz(theta) and vert(phi) angles by offset position.
* @param {Array<number>} offset The offset position.
* @returns {Array<number>} The [0] is horz angle and [1] is vert angle.
* @example
* // 计算向前方向的角度
* const forwardOffset = [0, 0, 10];
* const forwardAngles = THING.Math.getAnglesFromOffset(forwardOffset); // 返回 [0, 0]
*
* // 计算向上方向的角度
* const upOffset = [0, 5, 0];
* const upAngles = THING.Math.getAnglesFromOffset(upOffset); // 返回 [0, 90]
*
* // 计算向右方向的角度
* const rightOffset = [8, 0, 0];
* const rightAngles = THING.Math.getAnglesFromOffset(rightOffset); // 返回 [90, 0]
*
* // 在相机控制中使用
* const cameraPosition = [0, 0, 0];
* const targetPosition = [50, 30, 50];
* // 计算从相机到目标的偏移向量
* const offset = [
* targetPosition[0] - cameraPosition[0],
* targetPosition[1] - cameraPosition[1],
* targetPosition[2] - cameraPosition[2]
* ];
* // 获取相机需要旋转的角度以朝向目标
* const [horzAngle, vertAngle] = THING.Math.getAnglesFromOffset(offset);
*/
static getAnglesFromOffset(offset) {
if (!offset) {
return [0, 0];
}
vec3.normalize(_vec3_0, offset);
var theta = -Math.acos(_vec3_0[1]);
var phi = Math.atan2(_vec3_0[0], _vec3_0[2]);
var horzAngle = MathUtils.radToDeg(phi);
var vertAngle = (MathUtils.radToDeg(theta));
horzAngle = horzAngle - 180;
if (vertAngle > 0 && vertAngle < 0.001) {
vertAngle = 90;
}
else {
if (vertAngle < 90) {
vertAngle = 90 - vertAngle;
}
else {
vertAngle -= 90;
}
}
return [horzAngle, vertAngle];
}
/**
* project on vector.
* @param {Array<number>} out The recieving vector.
* @param {Array<number>} a The vector a.
* @param {Array<number>} b The vector b.
* @returns {Array<number>} the project result.
* @example
* // 将一个向量投影到另一个向量上
* const vectorA = [3, 4, 0]; // 要投影的向量
* const vectorB = [5, 0, 0]; // 投影基向量(x轴方向)
* const result = [0, 0, 0]; // 接收结果的向量
* THING.Math.projectOnVector(result, vectorA, vectorB); // result 变为 [3, 0, 0]
*
* // 计算一个向量在另一个向量方向上的分量
* const force = [10, 10, 0]; // 原始力向量
* const direction = [1, 0, 0]; // x轴方向
* const projection = [0, 0, 0]; // 接收结果的向量
* THING.Math.projectOnVector(projection, force, direction); // projection 变为 [10, 0, 0]
*
* // 在物理模拟中使用
* const velocity = [5, 8, 3]; // 物体速度
* const normal = [0, 1, 0]; // 表面法线(y轴方向)
* const normalComponent = [0, 0, 0]; // 接收结果的向量
* THING.Math.projectOnVector(normalComponent, velocity, normal); // 计算速度在法线方向的分量
*/
static projectOnVector(out, a, b) {
const denominator = vec3.length(b);
if (denominator === 0) {
return vec3.set(out, 0, 0, 0);
}
const scalar = vec3.dot(a, b) / denominator;
return vec3.scale(out, b, scalar);
}
/**
* 将向量投影到平面上。
* @param {Array<number>} out 接收结果的向量。
* @param {Array<number>} a 要投影的向量。
* @param {Array<number>} planeNormal 平面的法线向量。
* @returns {Array<number>} 投影结果。
* @example
* // 将向量投影到水平面上(去除y分量)
* const vector = [5, 10, 8]; // 原始向量
* const planeNormal = [0, 1, 0]; // 水平面法线(y轴方向)
* const result = [0, 0, 0]; // 接收结果的向量
* THING.Math.projectOnPlane(result, vector, planeNormal); // result 变为 [5, 0, 8]
*
* // 在物理模拟中使用
* const velocity = [3, 7, 2]; // 物体速度
* const surfaceNormal = [0, 1, 0]; // 表面法线
* const tangentialVelocity = [0, 0, 0]; // 接收结果的向量
* THING.Math.projectOnPlane(tangentialVelocity, velocity, surfaceNormal); // 计算沿表面的速度分量
*
* // 在相机控制中使用
* const cameraDirection = [1, 0.5, 1]; // 相机方向
* const upVector = [0, 1, 0]; // 上方向
* const horizontalDirection = [0, 0, 0]; // 接收结果的向量
* THING.Math.projectOnPlane(horizontalDirection, cameraDirection, upVector); // 获取相机的水平方向
*/
static projectOnPlane(out, a, planeNormal) {
MathUtils.projectOnVector(out, a, planeNormal);
out[0] = a[0] - out[0];
out[1] = a[1] - out[1];
out[2] = a[2] - out[2];
return out;
}
/**
* 将3D点转换为2D平面点。
* @param {Array<Array<number>>} points 3D点数组。
* @returns {Array<Array<number>>} 2D平面点数组。
* @example
* // 将3D点转换为2D平面点(忽略y坐标)
* const points3D = [
* [1, 5, 2],
* [3, 8, 4],
* [5, 2, 6]
* ];
* const points2D = THING.Math.toPlanePoints(points3D); // 返回 [[1, 2], [3, 4], [5, 6]]
*
* // 在地图或UI绘制中使用
* const worldPositions = [
* [10, 0, 20],
* [30, 0, 40],
* [50, 0, 60]
* ];
* const screenPositions = THING.Math.toPlanePoints(worldPositions); // 返回 [[10, 20], [30, 40], [50, 60]]
*
* // 处理已经是2D的点
* const alreadyPoints2D = [[1, 2], [3, 4], [5, 6]];
* const result = THING.Math.toPlanePoints(alreadyPoints2D); // 返回原数组的副本
*/
static toPlanePoints(points) {
if (!points || !points.length) {
return [];
}
var vertexLength = points[0].length;
if (!vertexLength) {
return [];
}
else if (vertexLength == 3) {
return points.map(point => {
return [point[0], point[2]];
});
}
return points.slice(0);
}
/**
* Lerp quaternion.
* @param {Array<number>} start The start quaterion.
* @param {Array<number>} end The end quaterion.
* @param {number} alpha The alpha between [0, 1]
* @returns {Array<number>} The interpolated quaternion as an array of four numbers representing the x, y, z, and w components.
* @example
* // 在两个四元数之间进行球面线性插值
* const startRotation = [0, 0, 0, 1]; // 初始旋转(单位四元数)
* const endRotation = [0, 0.7071, 0, 0.7071]; // 绕Y轴旋转90度的四元数
*
* // 计算中间旋转(alpha=0.5表示中间位置)
* const midRotation = THING.Math.slerp(startRotation, endRotation, 0.5);
*
* // 在动画中使用
* function animateRotation(progress) {
* // progress从0到1变化
* const currentRotation = THING.Math.slerp(startRotation, endRotation, progress);
* // 应用旋转到对象
* // object.quaternion = currentRotation;
* }
*
* // 在相机过渡中使用
* const cameraStartQuat = [0, 0, 0, 1];
* const cameraEndQuat = [0, 0.3826, 0, 0.9238]; // 绕Y轴旋转45度
* const smoothTransition = THING.Math.slerp(cameraStartQuat, cameraEndQuat, 0.3); // 平滑过渡
*/
static slerp(start, end, alpha) {
var target = [];
return quat.slerp(target, start, end, alpha);
}
/**
* Look at position.
* @param {Array<number>} eye The position of the viewer.
* @param {Array<number>} center The position where viewer is looking at.
* @param {Array<number>} up The up direction.
* @returns {Array<number>} The 4x4 matrix.
* @example
* // 创建一个朝向目标的矩阵
* const cameraPosition = [0, 5, 10]; // 相机位置
* const targetPosition = [0, 0, 0]; // 目标位置
* const upDirection = [0, 1, 0]; // 上方向
* const lookAtMatrix = THING.Math.lookAt(cameraPosition, targetPosition, upDirection);
*
* // 在相机控制中使用
* function updateCamera(position, target) {
* const matrix = THING.Math.lookAt(position, target, [0, 1, 0]);
* // 应用矩阵到相机
* // camera.matrix = matrix;
* }
*
* // 创建一个物体朝向另一个物体的矩阵
* const objectPosition = [5, 0, 5];
* const lookAtPosition = [0, 0, 0];
* const worldUpVector = [0, 1, 0];
* const orientationMatrix = THING.Math.lookAt(objectPosition, lookAtPosition, worldUpVector);
*/
static lookAt(eye, center, up) {
var target = [];
if (vec3.equals(eye, center)) {
mat4.copy(target, _identity_mat4);
}
else {
mat4.targetTo(target, eye, center, up);
// If we get the invalid result then try to fix number by very small tolerance
if (!target[0] || !target[5] || !target[10]) {
let tolerance = MathUtils.TOLERANCE;
mat4.targetTo(target, eye, [center[0] - tolerance, center[1] - tolerance, center[2] - tolerance], up);
}
}
return target;
}
/**
* 通过轴向返回以弧度表示的角度的四元数表示。
* @param {Array<number>} axis 轴向。
* @param {number} radian 弧度。
* @param {Array<number>} target 目标的引用值。
* @returns {Array<number>} The quaternion as an array of four numbers representing the x, y, z, and w components.
* @public
* @example
* // 创建一个绕Y轴旋转90度(π/2弧度)的四元数
* const yAxis = [0, 1, 0];
* const angle90Rad = Math.PI / 2;
* const result = [0, 0, 0, 0]; // 用于存储结果的数组
* THING.Math.getQuatFromAxisRadian(yAxis, angle90Rad, result); // result 变为 [0, 0.7071, 0, 0.7071]
*
* // 创建一个绕X轴旋转45度(π/4弧度)的四元数
* const xAxis = [1, 0, 0];
* const angle45Rad = Math.PI / 4;
* const xRotation = THING.Math.getQuatFromAxisRadian(xAxis, angle45Rad, [0, 0, 0, 0]);
*
* // 在物理模拟中使用
* const rotationAxis = [0, 0, 1]; // Z轴
* const rotationSpeed = 0.01; // 每帧旋转的弧度
* function updateRotation(deltaTime) {
* const rotationAmount = rotationSpeed * deltaTime;
* const deltaRotation = THING.Math.getQuatFromAxisRadian(rotationAxis, rotationAmount);
* // 应用旋转增量
* // object.quaternion = THING.Math.multiplyQuat(object.quaternion, deltaRotation);
* }
*/
static getQuatFromAxisRadian(axis, radian, target) {
const halfAngle = radian / 2, s = Math.sin(halfAngle);
target = target || [];
target[0] = axis[0] * s;
target[1] = axis[1] * s;
target[2] = axis[2] * s;
target[3] = Math.cos(halfAngle);
return target;
}
/**
* Returns a quaternion representation of an angle in degrees by axis.
* @param {Array<number>} axis The axis.
* @param {number} angle The angle.
* @param {Array<number>} target The referenced value of target.
* @returns {Array<number>} The quaternion as an array of four numbers representing the x, y, z, and w components.
* @example
* // 创建一个绕Y轴旋转90度的四元数
* const yAxis = [0, 1, 0];
* const angle90 = 90;
* const result = [0, 0, 0, 0]; // 用于存储结果的数组
* THING.Math.getQuatFromAxisAngle(yAxis, angle90, result); // result 变为 [0, 0.7071, 0, 0.7071]
*
* // 创建一个绕X轴旋转45度的四元数
* const xAxis = [1, 0, 0];
* const angle45 = 45;
* const xRotation = THING.Math.getQuatFromAxisAngle(xAxis, angle45, [0, 0, 0, 0]);
*
* // 在3D对象旋转中使用
* const rotationAxis = [0, 1, 0]; // Y轴
* const rotationAngle = 30; // 30度
* const objectRotation = THING.Math.getQuatFromAxisAngle(rotationAxis, rotationAngle);
* // 应用旋转到对象
* // object.quaternion = objectRotation;
*/
static getQuatFromAxisAngle(axis, angle, target) {
const radian = MathUtils.degToRad(angle);
return MathUtils.getQuatFromAxisRadian(axis, radian, target);
}
/**
* 将欧拉角('XYZ')以度数表示转换为四元数表示。
* @param {Array<number>} angles 欧拉角(以度数表示)。
* @param {Array<number>} target 目标的引用值。
* @returns {Array<number>} 四元数,作为四个数字的数组,分别代表x, y, z, 和w分量。
* @public
* @example
* // 创建一个从欧拉角转换的四元数
* const eulerAngles = [30, 45, 0]; // 绕X轴旋转30度,绕Y轴旋转45度
* const quaternion = THING.Math.getQuatFromAngles(eulerAngles); // 返回对应的四元数
*
* // 使用自定义目标数组
* const angles = [0, 90, 0]; // 绕Y轴旋转90度
* const result = [0, 0, 0, 1];
* THING.Math.getQuatFromAngles(angles, result); // result 被修改为对应的四元数
*
* // 在3D对象旋转中使用
* const objectRotation = [10, 20, 30]; // 绕X轴10度,绕Y轴20度,绕Z轴30度
* const objectQuaternion = THING.Math.getQuatFromAngles(objectRotation);
* // 应用旋转到对象
* // object.quaternion = objectQuaternion;
*/
static getQuatFromAngles(angles, target = [0, 0, 0, 1]) {
var halfToRad = 0.5 * Math.PI / 180.0;
var x = angles[0] * halfToRad;
var y = angles[1] * halfToRad;
var z = angles[2] * halfToRad;
return _eulerToQuat(target, x, y, z);
}
/**
* Returns a quaternion representation of an euler angle('XYZ') in radians.
* @param {Array<number>} euler The euler in radians.
* @param {Array<number>} target The referenced value of target.
* @returns {Array<number>} The quaternion as an array of four numbers representing the x, y, z, and w components.
* @example
* // 将欧拉角(弧度)转换为四元数
* const eulerRadians = [Math.PI/4, 0, 0]; // X轴旋转45度(π/4弧度)
* const quaternion = THING.Math.getQuatFromEuler(eulerRadians); // 返回对应的四元数
*
* // 使用自定义目标数组
* const euler = [0, Math.PI/2, 0]; // Y轴旋转90度(π/2弧度)
* const result = [0, 0, 0, 1];
* THING.Math.getQuatFromEuler(euler, result); // result被修改为对应的四元数
*
* // 在3D对象旋转中使用
* const rotationRadians = [Math.PI/6, Math.PI/4, 0]; // X轴30度,Y轴45度
* const objectQuaternion = THING.Math.getQuatFromEuler(rotationRadians);
* // 应用旋转到对象
* // object.quaternion = objectQuaternion;
* @public
*/
static getQuatFromEuler(euler, target = [0, 0, 0, 1]) {
var x = euler[0] * 0.5;
var y = euler[1] * 0.5;
var z = euler[2] * 0.5;
return _eulerToQuat(target, x, y, z);
}
/**
* Returns a quaternion representation of 4x4 matrix.
* @param {Array<number>} mat The 4x4 matrix.
* @returns {Array<number>} The quaternion as an array of four numbers representing the x, y, z, and w components.
* @example
* // 从旋转矩阵中提取四元数
* const rotationMatrix = [
* 1, 0, 0, 0,
* 0, 0, -1, 0,
* 0, 1, 0, 0,
* 0, 0, 0, 1
* ]; // 绕X轴旋转90度的矩阵
* const quaternion = THING.Math.getQuatFromMat4(rotationMatrix); // 返回对应的四元数
*
* // 从lookAt矩阵中提取旋转
* const eye = [0, 5, 10];
* const center = [0, 0, 0];
* const up = [0, 1, 0];
* const lookAtMatrix = THING.Math.lookAt(eye, center, up);
* const cameraRotation = THING.Math.getQuatFromMat4(lookAtMatrix);
*
* // 在3D变换中使用
* function extractRotation(transformMatrix) {
* const rotationQuat = THING.Math.getQuatFromMat4(transformMatrix);
* // 使用提取的旋转
* // object.quaternion = rotationQuat;
* }
* @public
*/
static getQuatFromMat4(mat) {
var target = [];
return mat4.getRotation(target, mat);
}
/**
* Get the quaternion from target and eye position.
* @param {Array<number>} eye The position of the viewer.
* @param {Array<number>} center The position where viewer is looking at.
* @param {Array<number>} up The up direction.
* @returns {Array<number>} The quaternion as an array of four numbers representing the x, y, z, and w components.
* @example
* // 从相机位置和目标位置计算四元数
* const eyePosition = [0, 5, 10]; // 相机位置
* const targetPosition = [0, 0, 0]; // 目标位置(相机看向的点)
* const upDirection = [0, 1, 0]; // 向上方向
* const quaternion = THING.Math.getQuatFromTarget(eyePosition, targetPosition, upDirection);
*
* // 在相机控制中使用
* function updateCameraRotation(camera, lookAtPoint) {
* const cameraPos = camera.position;
* const upDir = [0, 1, 0];
* // 计算相机的旋转四元数
* const rotation = THING.Math.getQuatFromTarget(cameraPos, lookAtPoint, upDir);
* // camera.quaternion = rotation;
* }
*
* // 在物体朝向控制中使用
* const objectPosition = [5, 0, 5];
* const targetPoint = [0, 0, 0];
* const worldUp = [0, 1, 0];
* // 计算物体需要的旋转,使其朝向目标点
* const objectRotation = THING.Math.getQuatFromTarget(objectPosition, targetPoint, worldUp);
* @public
*/
static getQuatFromTarget(eye, center, up) {
var mat = MathUtils.lookAtRH(center, eye, up);
return MathUtils.setFromRotationMatrix(mat);
}
/**
* 计算右手坐标系下的观察矩阵。
* @param {Array<number>} eye 观察者的位置。
* @param {Array<number>} target 观察者的目标位置。
* @param {Array<number>} up 观察者的上方向。
* @returns {Array<number>} 观察矩阵。
* @example
* const eyePosition = [0, 5, 10]; // 观察者的位置
* const targetPosition = [0, 0, 0]; // 观察者的目标位置
* const upDirection = [0, 1, 0]; // 观察者的上方向
* const lookAtMatrix = MathUtils.lookAtRH(eyePosition, targetPosition, upDirection);
* @public
*/
static lookAtRH(eye, target, up) {
const mat = mat4.create()
let direction = MathUtils.subVector(eye, target)
if (MathUtils.getVectorLengthSquared(direction) === 0) {
// eye and target are in the same position
direction[2] = 1;
}
direction = MathUtils.normalizeVector(direction)
let _x = MathUtils.crossVector(up, direction);
if (MathUtils.getVectorLengthSquared(_x) === 0) {
// up and z are parallel
if (Math.abs(up[2]) === 1) {
direction[0] += 0.0001;
}
else {
direction[2] += 0.0001;
}
direction = MathUtils.normalizeVector(direction)
_x = MathUtils.crossVector(up, direction);
}
_x = MathUtils.normalizeVector(_x)
let _y = MathUtils.crossVector(direction, _x);
mat[0] = _x[0]; mat[4] = _y[0]; mat[8] = direction[0];
mat[1] = _x[1]; mat[5] = _y[1]; mat[9] = direction[1];
mat[2] = _x[2]; mat[6] = _y[2]; mat[10] = direction[2];
return mat
}
/**
* 从旋转矩阵中提取四元数。
* @param {Array<number>} mat 旋转矩阵。
* @returns {Array<number>} 四元数。
* @example
* const rotationMatrix = [...]; // 旋转矩阵
* const quaternion = MathUtils.setFromRotationMatrix(rotationMatrix);
* @public
*/
static setFromRotationMatrix(mat) {
const quat = [0, 0, 0, 1]
const m11 = mat[0], m12 = mat[4], m13 = mat[8],
m21 = mat[1], m22 = mat[5], m23 = mat[9],
m31 = mat[2], m32 = mat[6], m33 = mat[10],
trace = m11 + m22 + m33;
let s;
if (trace > 0) {
s = 0.5 / Math.sqrt(trace + 1.0);
quat[3] = 0.25 / s;
quat[0] = (m32 - m23) * s;
quat[1] = (m13 - m31) * s;
quat[2] = (m21 - m12) * s;
}
else if (m11 > m22 && m11 > m33) {
s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);
quat[3] = (m32 - m23) / s;
quat[0] = 0.25 * s;
quat[1] = (m12 + m21) / s;
quat[2] = (m13 + m31) / s;
}
else if (m22 > m33) {
s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);
quat[3] = (m13 - m31) / s;
quat[0] = (m12 + m21) / s;
quat[1] = 0.25 * s;
quat[2] = (m23 + m32) / s;
}
else {
s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);
quat[3] = (m21 - m12) / s;
quat[0] = (m13 + m31) / s;
quat[1] = (m23 + m32) / s;
quat[2] = 0.25 * s;
}
return quat
}
/**
* 返回四元数的欧拉角表示
* @param {Array<number>} quat 四元数
* @param {Array<number>} target 目标的引用值
* @returns {Array<number>} 欧拉角作为三个数字的数组,分别代表x、y和z坐标
* @example
* // 将四元数转换为欧拉角(弧度)
* const quaternion = [0, 0.7071, 0, 0.7071]; // 绕Y轴旋转90度的四元数
* const eulerRadians = THING.Math.getEulerFromQuat(quaternion); // 返回 [0, π/2, 0]
*
* // 使用自定义目标数组
* const quat = [0, 0, 0.7071, 0.7071]; // 绕Z轴旋转90度的四元数
* const result = [0, 0, 0];
* THING.Math.getEulerFromQuat(quat, result); // result 现在包含欧拉角(弧度)
*
* // 在动画系统中使用
* function updateRotationUI(objectQuaternion) {
* const eulerAngles = THING.Math.getEulerFromQuat(objectQuaternion);
* // 显示角度(需要转换为度数)
* console.log('Rotation: ' +
* 'X=' + (eulerAngles[0] * 180/Math.PI) + '°, ' +
* 'Y=' + (eulerAngles[1] * 180/Math.PI) + '°, ' +
* 'Z=' + (eulerAngles[2] * 180/Math.PI) + '°'
* );
* }
* @public
*/
static getEulerFromQuat(quat, target) {
mat4.fromQuat(_mat4, quat);
const m11 = _mat4[0], m12 = _mat4[4], m13 = _mat4[8];
const m22 = _mat4[5], m23 = _mat4[9];
const m32 = _mat4[6], m33 = _mat4[10];
let euler = target || [0, 0, 0];
euler[1] = Math.asin(MathUtils.clamp(m13, -1, 1));
if (Math.abs(m13) < 0.9999999) {
euler[0] = Math.atan2(-m23, m33);
euler[2] = Math.atan2(-m12, m11);
}
else {
euler[0] = Math.atan2(m32, m22);
euler[2] = 0;
}
return euler;
}
/**
* 从四元数中获取角度。
* @param {Array<number>} quat 四元数。
* @param {Array<number>} target 目标的引用值。
* @returns {Array<number>} 欧拉角作为三个数字的数组,分别代表x、y和z坐标。
* @example
* // 将四元数转换为欧拉角(度)
* const quaternion = [0, 0.7071, 0, 0.7071]; // 绕Y轴旋转90度的四元数
* const eulerDegrees = THING.Math.getAnglesFromQuat(quaternion); // 返回 [0, 90, 0]
*
* // 使用自定义目标数组
* const quat = [0, 0, 0.7071, 0.7071]; // 绕Z轴旋转90度的四元数
* const result = [0, 0, 0];
* THING.Math.getAnglesFromQuat(quat, result); // result 变为 [0, 0, 90]
*
* // 在3D对象旋转中使用
* function displayObjectRotation(objectQuaternion) {
* const angles = THING.Math.getAnglesFromQuat(objectQuaternion);
* console.log(`物体旋转角度: X=${angles[0]}°, Y=${angles[1]}°, Z=${angles[2]}°`);
* }
*
* // 在相机控制中使用
* const cameraQuat = [0.2706, 0.6533, 0.2706, 0.6533]; // 某个相机旋转
* const cameraAngles = THING.Math.getAnglesFromQuat(cameraQuat);
* // 使用角度设置UI控件
* // xRotationSlider.value = cameraAngles[0];
* // yRotationSlider.value = cameraAngles[1];
* // zRotationSlider.value = cameraAngles[2];
* @public
*/
static getAnglesFromQuat(quat, target) {
var euler = MathUtils.getEulerFromQuat(quat, target);
euler[0] = MathUtils.radToDeg(euler[0]);
euler[1] = MathUtils.radToDeg(euler[1]);
euler[2] = MathUtils.radToDeg(euler[2]);
return euler;
}
/**
* 将像素坐标转换为屏幕坐标在[-1, 1]范围内。
* @param {Array<number>} position 位置。
* @param {Array<number>} size 画布的大小。
* @returns {Array<number>} 屏幕坐标在[-1, 1]范围内,作为代表x和y坐标的两个数字的数组。
* @example
* // 将像素坐标转换为标准化设备坐标(NDC)
* const canvasSize = [800, 600]; // 画布大小
* const pixelPos = [400, 300]; // 像素坐标(画布中心)
* const ndcPos = THING.Math.pixelToScreenCoordinate(pixelPos, canvasSize); // 返回 [0, 0]
*
* // 将鼠标位置转换为NDC坐标
* function handleMouseMove(event) {
* const mousePos = [event.clientX, event.clientY];
* const screenSize = [window.innerWidth, window.innerHeight];
* const normalizedPos = THING.Math.pixelToScreenCoordinate(mousePos, screenSize);
* // 使用标准化的坐标进行射线投射等操作
* }
*
* // 在WebGL渲染中使用
* const viewport = [1920, 1080]; // 视口大小
* const corner = [0, 0]; // 左上角
* const ndc = THING.Math.pixelToScreenCoordinate(corner, viewport); // 返回 [-1, 1]
* @public
*/
static pixelToScreenCoordinate(position, size) {
return [(position[0] / size[0]) * 2 - 1, -(position[1] / size[1]) * 2 + 1];
}
/**
* 将屏幕坐标[-1, 1]转换为像素坐标。
* @param {Array<number>} position 屏幕坐标[-1, 1]。
* @param {Array<number>} size 画布的大小。
* @returns {Array<number>} 像素坐标,作为代表x和y坐标的两个数字的数组。
* @example
* // 将标准化设备坐标(NDC)转换为像素坐标
* const canvasSize = [800, 600]; // 画布大小
* const ndcPos = [0, 0]; // NDC坐标(屏幕中心)
* const pixelPos = THING.Math.screenCoordinateToPixel(ndcPos, canvasSize); // 返回 [400, 300]
*
* // 在射线拾取结果处理中使用
* function handlePickingResult(ndcPosition) {
* const viewportSize = [1920, 1080];
* const pixelPosition = THING.Math.screenCoordinateToPixel(ndcPosition, viewportSize);
* console.log(`点击位置:x=${pixelPosition[0]}, y=${pixelPosition[1]}`);
* }
*
* // 转换边界点
* const viewport = [1024, 768];
* const topRight = [1, 1]; // NDC空间的右上角
* const pixels = THING.Math.screenCoordinateToPixel(topRight, viewport); // 返回 [1024, 0]
* @public
*/
static screenCoordinateToPixel(position, size) {
return [((position[0] + 1) / 2) * size[0], ((position[1] - 1) / -2) * size[1]];
}
/**
* 根据距离获取方向上的位置。
* @param {Array<number>} position 起始位置。
* @param {Array<number>} direction 方向。
* @param {number} scale 缩放因子。
* @returns {Array<number>} 方向上的位置,作为代表x、y和z坐标的三个数字的数组。
* @example
* // 获取在指定方向上按指定距离的点
* const startPos = [0, 0, 0];
* const direction = [1, 0, 0];
* const distance = 10;
* const result = THING.Math.getPositionOnDirection(startPos, direction, distance);
* // @expect(result === [10, 0, 0]);
* @public
*/
static getPositionOnDirection(position, direction, scale) {
return MathUtils.addVector(position, MathUtils.scaleVector(direction, scale));
}
/**
* 根据方向获取虚拟平面上的位置。
* @param {Array<number>} origin 起始(眼睛)位置。
* @param {Array<number>} target 平面上的目标位置。
* @param {Array<number>} direction 平面方向。
* @returns {Array<number>} 平面上的位置,作为代表x、y和z坐标的三个数字的数组。
* @example
* // 获取虚拟平面上根据方向的位置
* const eyePosition = [0, 0, 0]; // 眼睛位置
* const targetOnPlane = [10, 0, 0]; // 平面上的目标位置
* const planeDirection = [0, 1, 0]; // 平面方向(Y轴)
* const positionOnPlane = THING.Math.getPositionOnPlane(eyePosition, targetOnPlane, planeDirection);
* // @expect(positionOnPlane === [10, 0, 0]);
* @public
*/
static getPositionOnPlane(origin, target, direction) {
vec3.subtract(_vec3_1, origin, target);
MathUtils.projectOnPlane(_vec3_2, _vec3_1, direction);
vec3.add(_vec3_1, _vec3_2, target);
return [_vec3_1[0], _vec3_1[1], _vec3_1[2]];
}
/**
* 创建一个新的三维向量 [0,0,0]。
* @returns {Array<number>} 返回一个包含3个元素的数组,表示三维向量。
* @public
* @example
* // 创建一个新的三维向量(表示原点)
* const vector = THING.Math.createVec3(); // 返回 [0, 0, 0]
*
* // 在3D对象初始化中使用
* const position = THING.Math.createVec3();
* // object.position = position;
*
* // 用作相机位置的起始值
* const cameraPosition = THING.Math.createVec3();
* // 进行相机位置计算...
*/
static createVec3() {
return [0, 0, 0];
}
/**
* 创建一个新的四元数 [0,0,0,1]。
* @returns {Array<number>} 返回一个包含4个元素的数组,表示四元数。
* @public
* @example
* // 创建一个新的四元数(表示无旋转)
* const quaternion = THING.Math.createQuat(); // 返回 [0, 0, 0, 1]
*
* // 在3D对象旋转初始化中使用
* const rotation = THING.Math.createQuat();
* // object.quaternion = rotation;
*
* // 用作旋转插值的起始值
* const startRotation = THING.Math.createQuat();
* // 进行四元数插值计算...
*/
static createQuat() {
return [0, 0, 0, 1];
}
/**
* 创建一个新的欧拉角 [0,0,0]。
* @returns {Array<number>} 返回一个包含3个元素的数组,表示欧拉角(以弧度为单位)。
* @public
* @example
* // 创建一个新的欧拉角(表示无旋转)
* const euler = THING.Math.createEuler(); // 返回 [0, 0, 0]
*
* // 在相机旋转中使用
* const cameraRotation = THING.Math.createEuler();
* // camera.rotation = cameraRotation;
*
* // 初始化物体的旋转角度
* const objectRotation = THING.Math.createEuler();
* // 可以之后修改各个轴的旋转角度
* objectRotation[0] = Math.PI/2; // 绕X轴旋转90度
*/
static createEuler() {
return [0, 0, 0];
}
/**
* 创建一个新的3x3矩阵(单位矩阵)。
* @returns {Array<number>} 返回一个包含9个元素的数组,表示3x3矩阵。
* @public
* @example
* // 创建一个新的3x3单位矩阵
* const matrix = THING.Math.createMat3();
* // 返回 [
* // 1, 0, 0,
* // 0, 1, 0,
* // 0, 0, 1
* // ]
*
* // 在2D变换中使用
* const transform2D = THING.Math.createMat3();
* // 可以用于2D旋转、缩放等操作
*
* // 用于法线矩阵计算
* const normalMatrix = THING.Math.createMat3();
* // 进行法线变换计算...
*/
static createMat3() {
return [
1, 0, 0,
0, 1, 0,
0, 0, 1
];
}
/**
* Create matrix4.
* @param {Array<number>} position The position.
* @param {Array<number>} angles The angles in degrees.
* @param {Array<number>} scale The scale.
* @returns {Array<number>} The matrix4 as an array of sixteen numbers representing the elements of the 4x4 matrix.
* @example
* // 创建一个单位矩阵(不带参数)
* const identityMatrix = THING.Math.createMat4();
* // 返回 [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
*
* // 创建一个带有位置、旋转和缩放的变换矩阵
* const position = [10, 5, 0]; // 位置
* const rotation = [0, 90, 0]; // 绕Y轴旋转90度
* const scale = [2, 1, 1]; // X轴方向缩放2倍
* const transformMatrix = THING.Math.createMat4(position, rotation, scale);
*
* // 在对象变换中使用
* function createObjectMatrix(objectPosition, objectRotation, objectScale) {
* return THING.Math.createMat4(objectPosition, objectRotation, objectScale);
* }
*
* // 在相机设置中使用
* const cameraPos = [0, 10, 20];
* const cameraRot = [30, 0, 0]; // 向下倾斜30度
* const cameraScale = [1, 1, 1]; // 通常相机不需要缩放
* const cameraMatrix = THING.Math.createMat4(cameraPos, cameraRot, cameraScale);
* // 应用到相机
* // camera.matrix = cameraMatrix;
*/
static createMat4(position, angles, scale) {
if (arguments.length) {
let quat = MathUtils.getQuatFromAngles(angles, _quat);
return mat4.fromRotationTranslationScale(MathUtils.createMat4(), quat, position, scale);
}
else {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
];
}
}
/**
* 获取线段上的一点。
* @param {Array<number>} start 线段的起点。
* @param {Array<number>} end 线段的终点。
* @param {number} target 目标点在线段上的位置,0为起点,1为终点,其他值为线段上的点。
* @returns {Array<number>} 线段上目标点的坐标。
* @example
* const start = [0, 0, 0]; // 线段起点
* const end = [10, 10, 10]; // 线段终点
* const target = 0.5; // 目标点在线段上的位置
* const pointOnLine = MathUtils.getPointOnLine(start, end, target);
* // pointOnLine 将是线段上距离起点50%的点的坐标
* @public
*/
static getPointOnLine(start, end, target) {
let result;
if (target === 1) {
result = end.concat();
}
else if (target == 0) {
result = start.concat();
}
else {
result = MathUtils.subVector(end, start);
result = MathUtils.scaleVector(result, target);
result = MathUtils.addVector(start, result);
}
return result;
}
/**
* 从两个单位向量中获取四元数。
* @param {Array<number>} from 起始向量。
* @param {Array<number>} to 终止向量。
* @returns {Array<number>} 一个四元数,用于描述从起始向量到终止向量的旋转。
* @example
* const from = [0, 0, 1]; // 起始向量
* const to = [0, 1, 0]; // 终止向量
* const quat = MathUtils.getQuatFromUnitVectors(from, to);
* // quat 将是从起始向量到终止向量的旋转四元数
*/
static getQuatFromUnitVectors(from, to) {
// assumes direction vectors from and to are normalized
let result = [];
let r = MathUtils.dotVector(from, to) + 1;
if (r < Number.EPSILON) {
// from and to point in opposite directions
r = 0;
if (Math.abs(from[0]) > Math.abs(from[2])) {
result[0] = -from[1];
result[1] = from[0];
result[2] = 0;
result[3] = r;
}
else {
result[0] = 0;
result[1] = -from[2];
result[2] = from[1];
result[3] = r;
}
}
else {
// crossVectors( from, to ); // inlined to avoid cyclic dependency on Vector3
result[0] = from[1] * to[2] - from[2] * to[1];
result[1] = from[2] * to[0] - from[0] * to[2];
result[2] = from[0] * to[1] - from[1] * to[0];
result[3] = r;
}
return MathUtils.normalizeQuat(result);
}
/**
* 标准化四元数。
* @param {Array<number>} quat 四元数。
* @returns {Array<number>} 标准化后的四元数。
* @example
* const quat = [0.5, 0.5, 0.5, 0.5]; // 四元数
* const normalizedQuat = MathUtils.normalizeQuat(quat);
* // normalizedQuat 将是标准化后的四元数
*/
static normalizeQuat(quat) {
let result = [];
let l = MathUtils.getQuatLength(quat);
if (l === 0) {
result[0] = 0;
result[1] = 0;
result[2] = 0;
result[3] = 1;
}
else {
l = 1 / l;
result[0] = quat[0] * l;
result[1] = quat[1] * l;
result[2] = quat[2] * l;
result[3] = quat[3] * l;
}
return result;
}
/**
* 计算四元数的长度。
* @param {Array<number>} quat 四元数。
* @returns {number} 四元数的长度。
* @public
* @example
* const quat = [0.5, 0.5, 0.5, 0.5]; // 四元数
* const length = MathUtils.getQuatLength(quat);
* // length 将是四元数的长度
*/
static getQuatLength(quat) {
return Math.sqrt(quat[0] * quat[0] + quat[1] * quat[1] + quat[2] * quat[2] + quat[3] * quat[3]);
}
/**
* 计算四元数的乘法。
* @param {Array<number>} a 第一个四元数。
* @param {Array<number>} b 第二个四元数。
* @returns {Array<number>} 结果四元数。
* @public
* @example
* // 计算两个四元数的乘积
* const q1 = [0.5, 0.5, 0.5, 0.5];
* const q2 = [0.5, 0.5, 0.5, 0.5];
* const result = THING.Math.multiplyQuat(q1, q2);
* // @expect(result === [0.5, 0.5, 0.5, 0.5]);
*/
static multiplyQuat(a, b) {
const qax = a[0], qay = a[1], qaz = a[2], qaw = a[3];
const qbx = b[0], qby = b[1], qbz = b[2], qbw = b[3];
var x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
var y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
var z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
var w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
return [x, y, z, w];
}
/**
* 将四元数应用于三维向量。
* @param {Array<number>} vector3 三维向量。
* @param {Array<number>} quaternion 四元数。
* @param {Array<number>} [out] 输出向量,缺省时将返回一个新的向量。
* @returns {Array<number>} 应用四元数后的向量。
* @example
* const vector3 = [1, 2, 3]; // 三维向量
* const quaternion = [0.5, 0.5, 0.5, 0.5]; // 四元数
* const result = MathUtils.vec3ApplyQuat(vector3, quaternion);
* // result 将是应用四元数后的向量
*/
static vec3ApplyQuat(vector3, quaternion, out) {
const x = vector3[0], y = vector3[1], z = vector3[2];
const qx = quaternion[0], qy = quaternion[1], qz = quaternion[2], qw = quaternion[3];
// calculate quat * vector
const ix = qw * x + qy * z - qz * y;
const iy = qw * y + qz * x - qx * z;
const iz = qw * z + qx * y - qy * x;
const iw = -qx * x - qy * y - qz * z;
// calculate result * inverse quat
if (out === undefined) {
out = [0, 0, 0];
}
out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
return out;
}
/**
* Decomposes this matrix into it's position, quaternion and scale components.
* @param {Array<number>} matrix The matrix to decompose.
* @param {Array<number>} position The position component.
* @param {Array<number>} quaternion The quaternion component.
* @param {Array<number>} scale The scale component.
* @returns {Array<number>} The decomposed matrix.
* @example
* // 分解矩阵
* const matrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
* const position = [0, 0, 0];
* const quaternion = [0, 0, 0, 1];
* const scale = [1, 1, 1];
* const result = THING.Math.decomposeFromMat4(matrix, position, quaternion, scale);
* // @expect(result === [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
*/
static decomposeFromMat4(matrix, position, quaternion, scale) {
let sx = MathUtils.hypot(matrix[0], matrix[1], matrix[2]);
let sy = MathUtils.hypot(matrix[4], matrix[5], matrix[6]);
let sz = MathUtils.hypot(matrix[8], matrix[9], matrix[10]);
// if determine is negative, we need to invert one scale
const det = determinantToMat4(matrix);
if (det < 0) sx = -sx;
position[0] = matrix[12];
position[1] = matrix[13];
position[2] = matrix[14];
// scale the rotation part
const _m1 = [...matrix];
// Remove scale to obtain a rotation matrix without scale influence, used for extracting rotation
const invSX = 1 / sx;
const invSY = 1 / sy;
const invSZ = 1 / sz;
_m1[0] *= invSX;
_m1[1] *= invSX;
_m1[2] *= invSX;
_m1[4] *= invSY;
_m1[5] *= invSY;
_m1[6] *= invSY;
_m1[8] *= invSZ;
_m1[9] *= invSZ;
_m1[10] *= invSZ;
// Sets this quaternion from rotation component of m.
const m11 = _m1[0], m12 = _m1[4], m13 = _m1[8],
m21 = _m1[1], m22 = _m1[5], m23 = _m1[9],
m31 = _m1[2], m32 = _m1[6], m33 = _m1[10],
trace = m11 + m22 + m33;
let s;
if (trace > 0) {
s = 0.5 / Math.sqrt(trace + 1.0);
quaternion[3] = 0.25 / s;
quaternion[0] = (m32 - m23) * s;
quaternion[1] = (m13 - m31) * s;
quaternion[2] = (m21 - m12) * s;
}
else if (m11 > m22 && m11 > m33) {
s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);
quaternion[3] = (m32 - m23) / s;
quaternion[0] = 0.25 * s;
quaternion[1] = (m12 + m21) / s;
quaternion[2] = (m13 + m31) / s;
}
else if (m22 > m33) {
s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);
quaternion[3] = (m13 - m31) / s;
quaternion[0] = (m12 + m21) / s;
quaternion[1] = 0.25 * s;
quaternion[2] = (m23 + m32) / s;
}
else {
s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);
quaternion[3] = (m21 - m12) / s;
quaternion[0] = (m13 + m31) / s;
quaternion[1] = (m23 + m32) / s;
quaternion[2] = 0.25 * s;
}
scale[0] = sx;
scale[1] = sy;
scale[2] = sz;
return matrix;
// Computes and returns the determinant of this matrix.
function determinantToMat4(m) {
const n11 = m[0], n12 = m[4], n13 = m[8], n14 = m[12];
const n21 = m[1], n22 = m[5], n23 = m[9], n24 = m[13];
const n31 = m[2], n32 = m[6], n33 = m[10], n34 = m[14];
const n41 = m[3], n42 = m[7], n43 = m[11], n44 = m[15];
return (
n41 * (+n14 * n23 * n32 -
n13 * n24 * n32 -
n14 * n22 * n33 +
n12 * n24 * n33 +
n13 * n22 * n34 -
n12 * n23 * n34
) +
n42 * (+n11 * n23 * n34 -
n11 * n24 * n33 +
n14 * n21 * n33 -
n13 * n21 * n34 +
n13 * n24 * n31 -
n14 * n23 * n31
) +
n43 * (+n11 * n24 * n32 -
n11 * n22 * n34 -
n14 * n21 * n32 +
n12 * n21 * n34 +
n14 * n22 * n31 -
n12 * n24 * n31
) +
n44 * (-n13 * n22 * n31 -
n11 * n23 * n32 +
n11 * n22 * n33 +
n13 * n21 * n32 -
n12 * n21 * n33 +
n12 * n23 * n31
)
);
}
}
/**
* 将位置、四元数和缩放合成到一个目标矩阵中。
* @param {Array<number>} position 位置数组,长度为3。
* @param {Array<number>} quaternion 四元数数组,长度为4。
* @param {Array<number>} scale 缩放数组,长度为3。
* @param {Array<number>} [target] 目标矩阵数组,长度为16,默认为空数组。
* @returns {Array<number>} 合成后的矩阵。
* @example
* const position = [1, 2, 3]; // 位置
* const quaternion = [0, 0, 0, 1]; // 四元数
* const scale = [1, 1, 1]; // 缩放
* const target = []; // 目标矩阵
* MathUtils.composeToMat4(position, quaternion, scale, target);
* // target 将被修改为合成后的矩阵
*/
static composeToMat4(position, quaternion, scale, target) {
target = target || [];
return mat4.fromRotationTranslationScale(target, quaternion, position, scale);
}
/**
* Transform a point from local space to world space.
* @param {Array<number>} matrixWorld The world transformation matrix.
* @param {Array<number>} position The local position to transform.
* @param {boolean} ignoreScale Whether to ignore scale transformation.
* @param {Array<number>} [target] Optional target array.
* @returns {Array<number>} The transformed position in world space.
* @example
* // 将局部坐标转换为世界坐标
* const worldMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 0, 0, 1];
* const localPos = [1, 0, 0];
* const worldPos = THING.Math.selfToWorld(worldMatrix, localPos, false);
* // 返回 [11, 0, 0]
*/
static selfToWorld(matrixWorld, position, ignoreScale, target) {
target = target || [];
if (ignoreScale) {
mat4.getTranslation(_position, matrixWorld);
mat4.getScaling(_scale, matrixWorld);
mat4.getRotation(_quat, matrixWorld);
mat4.fromRotationTranslationScale(_mat4, _quat, _position, _original_scale);
vec3.transformMat4(target, position, _mat4);
}
else {
vec3.transformMat4(target, position, matrixWorld);
}
return target;
}
/**
* Transform a point from world space to local space.
* @param {Array<number>} matrixWorld The world transformation matrix.
* @param {Array<number>} position The world position to transform.
* @param {boolean} ignoreScale Whether to ignore scale transformation.
* @param {Array<number>} [target] Optional target array.
* @returns {Array<number>} The transformed position in local space.
* @example
* // 将世界坐标转换为局部坐标
* const worldMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 0, 0, 1];
* const worldPos = [11, 0, 0];
* const localPos = THING.Math.worldToSelf(worldMatrix, worldPos, false);
* // 返回 [1, 0, 0]
*/
static worldToSelf(matrixWorld, position, ignoreScale, target) {
target = target || [];
if (ignoreScale) {
mat4.getTranslation(_position, matrixWorld);
mat4.getScaling(_scale, matrixWorld);
mat4.getRotation(_quat, matrixWorld);
mat4.fromRotationTranslationScale(_mat4_2, _quat, _position, _original_scale);
mat4.invert(_mat4_1, _mat4_2);
}
else {
mat4.invert(_mat4_1, matrixWorld);
}
vec3.transformMat4(target, position, _mat4_1);
return target;
}
/**
* 计算射线与平面的交点。
* @param {Array<number>} origin 射线的起点。
* @param {Array<number>} direction 射线的方向(已标准化)。
* @param {Array<number>} normal 平面的法线(已标准化)。
* @param {number} constant 平面的常数(起点的距离)。
* @returns {Array<number>|null} 交点或如果没有交点则返回null。
* @example
* // 计算射线与平面的交点
* const rayOrigin = [0, 0, 0];
* const rayDirection = [0, 0, 1];
* const planeNormal = [0, 0, 1];
* const planeConstant = 5;
* const intersection = THING.Math.intersectPlane(rayOrigin, rayDirection, planeNormal, planeConstant);
* // 返回 [0, 0, 5]
* @public
*/
static intersectPlane(origin, direction, normal, constant) {
let result = [];
const denominator = MathUtils.dotVector(normal, direction);
const t = -(MathUtils.dotVector(origin, normal) + constant) / denominator;
if (t < 0) { return null; }
result[0] = direction[0] * t + origin[0];
result[1] = direction[1] * t + origin[1];
result[2] = direction[2] * t + origin[2];
return result;
}
/**
* 检查两个轴对齐的边界框是否相交。
* @param {Array<number>} min1 第一个框的最小点。
* @param {Array<number>} max1 第一个框的最大点。
* @param {Array<number>} min2 第二个框的最小点。
* @param {Array<number>} max2 第二个框的最大点。
* @returns {boolean} 如果框相交返回true。
* @example
* // 检查两个AABB包围盒是否相交
* const box1Min = [0, 0, 0];
* const box1Max = [2, 2, 2];
* const box2Min = [1, 1, 1];
* const box2Max = [3, 3, 3];
* const isIntersecting = THING.Math.intersectsBox(box1Min, box1Max, box2Min, box2Max);
* // 返回 true
* @public
*/
static intersectsBox(min1, max1, min2, max2) {
return max1[0] < min2[0] || min1[0] > max2[0] ||
max1[1] < min2[1] || min1[1] > max2[1] ||
max1[2] < min2[2] || min1[2] > max2[2] ? false : true;
}
/**
* 检查点是否在轴对齐的边界框内。
* @param {Array<number>} point 要检查的点。
* @param {Array<number>} min 盒子的最小点。
* @param {Array<number>} max 盒子的最大点。
* @returns {boolean} 如果点在盒子内返回true。
* @example
* // 检查点是否在包围盒内
* const point = [1, 1, 1];
* const boxMin = [0, 0, 0];
* const boxMax = [2, 2, 2];
* const isInside = THING.Math.intersectsPoint(point, boxMin, boxMax);
* // 返回 true
* @public
*/
static intersectsPoint(point, min, max) {
return point[0] < min[0] || point[0] > max[0] ||
point[1] < min[1] || point[1] > max[1] ||
point[2] < min[2] || point[2] > max[2] ? false : true;
}
/**
* 将点吸附到工作平面网格上。
* @param {Array<number>} point 要吸附的点。
* @param {object} workPlane 包含位置、四元数和精度的工作平面。
* @returns {Array<number>} 吸附后的点。
* @example
* // 将点吸附到网格
* const point = [1.2, 0.8, 2.3];
* const workPlane = {
* position: [0, 0, 0],
* quaternion: [0, 0, 0, 1],
* precision: 1 // 网格大小为1
* };
* const snapped = THING.Math.snapPoint(point, workPlane);
*
* // 在CAD编辑中使用
* function snapToGrid(mousePosition) {
* const grid = {
* position: [0, 0, 0],
* quaternion: [0, 0, 0, 1],
* precision: 0.5 // 0.5单位网格
* };
* return THING.Math.snapPoint(mousePosition, grid);
* }
*
* // 在精确定位中使用
* const workPlane = {
* position: [10, 0, 10], // 网格原点
* quaternion: [0, 0, 0, 1],
* precision: 0.1 // 精确到0.1单位
* };
* const position = [10.23, 0, 9.87];
* const snappedPos = THING.Math.snapPoint(position, workPlane);
* @public
*/
static snapPoint(point, workPlane) {
const angles = MathUtils.getAnglesFromQuat(workPlane.quaternion);
const workPlaneMatrix = MathUtils.createMat4(workPlane.position, angles, [1, 1, 1]);
const workPlaneInverseMatrix = mat4.invert([], workPlaneMatrix);
const workPlanePoint = vec3.transformMat4([], point, workPlaneInverseMatrix);
const precision = Math.max(0, workPlane.precision);
const snappedPoint = [];
for (let i = 0; i < 3; i++) {
const _data = workPlanePoint[i] * 0.1;
const _integer = MathUtils.floor(_data);
const temp = _data - _integer + precision * 0.1 * 0.5;
const _decimal = precision > 0 ? temp - temp % (precision * 0.1) : temp;
snappedPoint[i] = (_integer + _decimal) * 10;
}
const finalPoint = vec3.transformMat4([], snappedPoint, workPlaneMatrix);
return finalPoint;
}
/**
* 获取路径的归一化步长数组。
* @param {Array<Array<number>>} points 路径点。
* @returns {Array<number>} 每段路径占总长度的比例。
* @example
* // 计算路径的归一化步长
* const path = [
* [0, 0, 0],
* [10, 0, 0],
* [10, 10, 0]
* ];
* const steps = THING.Math.getPointsSteps(path);
* // 返回每段路径占总长度的比例,如 [0.5, 0.5]
*
* // 在动画路径中使用
* function createPathAnimation(points) {
* const steps = THING.Math.getPointsSteps(points);
* // 使用steps来计算每段路径的动画时间
* return { path: points, segmentRatios: steps };
* }
*
* // 计算不等长路径的步长
* const complexPath = [
* [0, 0, 0],
* [10, 0, 0], // 长度10
* [10, 5, 0], // 长度5
* [0, 5, 0] // 长度10
* ];
* const pathSteps = THING.Math.getPointsSteps(complexPath);
* // 返回 [0.4, 0.2, 0.4] 表示每段路径的比例
* @public
*/
static getPointsSteps(points) {
// Get the total distance of path
let totalDistance = MathUtils.getDistanceFromPoints(points);
if (!totalDistance) {
return [];
}
let steps = [];
for (let i = 0; i < points.length - 1; i++) {
let distance = MathUtils.getDistance(points[i], points[i + 1]);
steps.push(distance / totalDistance);
}
return steps;
}
/**
* 根据进度获取数组索引。
* @param {Array} array 输入数组。
* @param {number} progress 进度值,介于0和1之间。
* @param {Array<number>} [steps] 可选的步长比例数组。
* @returns {number} 计算出的数组索引。
* @example
* // 根据进度获取数组索引
* const array = ['a', 'b', 'c', 'd'];
* const progress = 0.5;
* const index = THING.Math.getArrayIndexFromProgress(array, progress);
* // 返回 2
* @public
*/
static getArrayIndexFromProgress(array, progress, steps) {
if (!array.length) {
return -1;
}
if (progress >= 1) {
return array.length - 1;
}
if (steps) {
let curProgress = 0;
for (let i = 0; i < steps.length; i++) {
let step = steps[i];
if(!step){
continue;
}
let endProgress = curProgress + step;
if (endProgress >= progress) {
return i;
}
curProgress += step;
}
// Something wrong ...
return curProgress;
}
else {
let step = 1 / (array.length - 1);
let index = MathUtils.floor(progress / step);
return MathUtils.min(index, array.length - 1);
}
}
/**
* 获取当前数组段内的进度。
* @param {Array} array 输入数组。
* @param {number} progress 总体进度值,介于0和1之间。
* @param {Array<number>} [steps] 可选的步长比例数组。
* @returns {number} 当前段内的进度(0到1)。
* @example
* // 获取当前数组段内的进度
* const array = ['a', 'b', 'c', 'd'];
* const progress = 0.6;
* const segmentProgress = THING.Math.getArrayProgress(array, progress);
* // 返回当前段内的进度值
* @public
*/
static getArrayProgress(array, progress, steps) {
if (!array.length) {
return 0;
}
if (progress >= 1) {
return 1;
}
if (steps) {
let curProgress = 0;
for (let i = 0; i < steps.length; i++) {
let step = steps[i];
if (!step) {
continue;
}
let endProgress = curProgress + step;
let deltaProgress = endProgress - progress;
if (deltaProgress >= 0) {
return 1 - (deltaProgress / step);
}
curProgress += step;
}
return 0;
}
else {
let step = 1 / (array.length - 1);
return MathUtils.fract(progress / step);
}
}
/**
* 沿路径插值点。
* @param {Array<Array<number>>} points 路径点数组。
* @param {Array<number>} [steps] 可选的步长比例数组。
* @param {number} progress 进度值,介于0和1之间。
* @returns {object} 包含插值点和段信息的对象。
* @example
* // 在路径上插值计算点位置
* const path = [[0, 0, 0], [10, 0, 0], [10, 10, 0]];
* const progress = 0.5;
* const result = THING.Math.lerpPoints(path, null, progress);
* // 返回插值点和相关信息
* @public
*/
static lerpPoints(points, steps, progress) {
// Limit range progress for array index
progress = MathUtils.clamp(progress, 0, 1);
let from, to, index = -1;
// Get the target position
if (progress == 1) {
index = points.length - 1;
// The source(from) and target(to) position
from = points[index - 1];
to = points[index];
MathUtils.vec3.copy(_vec3_0, points[index]);
}
else {
// 映射进度
// progress = progress * (points.length - 1) / points.length;
index = MathUtils.getArrayIndexFromProgress(points, progress, steps);
// Get the position by progress
let rangeProgress = MathUtils.getArrayProgress(points, progress, steps);
// The source(from) and target(to) position
from = points[index];
to = points[index + 1];
MathUtils.vec3.lerp(_vec3_0, from, to, rangeProgress);
}
return {
point: _vec3_0,
index,
from,
to
};
}
/**
* @typedef {object} Spherical
* @property {number} [radius=1] 球体半径。
* @property {number} [phi=0] 从y轴(上)开始的极角,单位为弧度。
* @property {number} [theta=0] 绕y轴(上)开始的赤道角,单位为弧度。
*/
/**
* 从笛卡尔坐标生成球面坐标的radius、phi和theta属性。
* @param {Array<number>} vector 坐标。
* @returns {Spherical} 球面坐标。
* @example
* // 将笛卡尔坐标转换为球面坐标
* const vector = [1, 1, 1];
* const spherical = THING.Math.makeSphericalFromCartesianCoords(vector);
* // @expect(spherical.radius === Math.sqrt(3));
* // @expect(spherical.phi === Math.PI / 4);
* // @expect(spherical.theta === Math.PI / 4);
* @public
*/
static makeSphericalFromCartesianCoords(vector) {
var spherical = { radius: 1, theta: 0, phi: 0 }
var x = vector[0];
var y = vector[1];
var z = vector[2];
spherical.radius = Math.sqrt(x * x + y * y + z * z);
if (spherical.radius === 0) {
spherical.theta = 0;
spherical.phi = 0;
}
else {
spherical.theta = Math.atan2(x, z);
spherical.phi = Math.acos(MathUtils.clamp(y / spherical.radius, -1, 1));
}
return spherical;
}
/**
* 将极角phi限制在0.000001到pi - 0.000001之间。
* @param {Spherical} spherical 一个点的球面坐标。
* @returns {Spherical} 球面坐标。
* @example
* // 限制球面坐标的极角
* const spherical = { radius: 1, phi: Math.PI / 2, theta: 0 };
* const safeSpherical = THING.Math.makeSphericalSafe(spherical);
* // @expect(safeSpherical.phi === Math.PI / 2);
* @public
*/
static makeSphericalSafe(spherical = { radius: 1, phi: 0, theta: 0 }) {
const EPS = 0.000001;
spherical.phi = Math.max(EPS, Math.min(Math.PI - EPS, spherical.phi));
return spherical;
}
/**
* 从球面坐标的半径、phi和theta获取向量。
* @param {Spherical} spherical 一个点的球面坐标。
* @returns {Array<number>} 向量。
* @example
* // 将球面坐标转换为笛卡尔坐标
* const spherical = { radius: 1, phi: Math.PI / 4, theta: Math.PI / 4 };
* const vector = THING.Math.getFromSphericalCoords(spherical);
* // @expect(vector[0] === 0.5);
* // @expect(vector[1] === 0.5);
* // @expect(vector[2] === 0.5);
* @public
*/
static getFromSphericalCoords(spherical = { radius: 1, phi: 0, theta: 0 }) {
const sinPhiRadius = Math.sin(spherical.phi) * spherical.radius;
var x = sinPhiRadius * Math.sin(spherical.theta);
var y = Math.cos(spherical.phi) * spherical.radius;
var z = sinPhiRadius * Math.cos(spherical.theta);
return [x, y, z];
}
/**
* 判断点是否在直线段上。
* @param {Array<number>} pointA - 直线段点A。
* @param {Array<number>} pointB - 直线段点B。
* @param {Array<number>} point - 测试点。
* @param {number} [deviation=0.001] 偏差。
* @param {boolean} [containsEndpoint=true] - 默认包含端点。
* @returns {boolean} 如果点在直线段上,则返回true,否则返回false。
* @example
* const inLineSegment = THING.MathUtils.pointInLineSegment([10,0],[10,10],[10,5]);
* console.log(inLineSegment);
* @public
*/
static pointInLineSegment(pointA, pointB, point, deviation, containsEndpoint) {
deviation = (deviation !== undefined) ? deviation : 0.001;
containsEndpoint = (containsEndpoint !== undefined) ? containsEndpoint : true;
if ((point[0] == pointA[0] && point[1] == pointA[1]) || (point[0] == pointB[0] && point[1] == pointB[1])) {
return containsEndpoint;
}
if (MathUtils.getDistance(point, pointA) < deviation) {
return true;
}
if (containsEndpoint && MathUtils.getDistance(point, pointB) < deviation) {
return true;
}
return (MathUtils.pointToLineDistance(pointA, pointB, point) < deviation) && MathUtils.projectionInside(pointA, pointB, point, containsEndpoint);
}
/**
* 点与线之间的距离。
* @param {Array<number>} pointA - 线段点A。
* @param {Array<number>} pointB - 线段点B。
* @param {Array<number>} point - 测试点。
* @returns {number} 距离。
* @example
* const distance = THING.MathUtils.pointToLineDistance([10,0],[10,10],[10,5]);
* console.log(distance);
* @public
*/
static pointToLineDistance(pointA, pointB, point) {
let k, b, d;
if (pointB[0] === pointA[0]) {
k = Infinity;
b = pointB[0];
}
else {
k = (pointB[1] - pointA[1]) / (pointB[0] - pointA[0]);
b = pointB[1] - k * pointB[0];
}
if (k !== Infinity) {
d = Math.abs(k * point[0] - point[1] + b) / Math.sqrt(k * k + 1);
}
else {
d = Math.abs(b - point[0]);
}
return d;
}
/**
* 检查点到线段的投影是否在内部。
* @param {Array<number>} pointA - 线段点A。
* @param {Array<number>} pointB - 线段点B。
* @param {Array<number>} point - 测试点。
* @param {boolean} [containsEndpoint=true] - 默认包含端点。
* @returns {boolean} 如果点到线段的投影在内部,则返回true,否则返回false。
* @example
* const inside = THING.MathUtils.projectionInside([10,0],[10,10],[10,5]);
* console.log(inside);
* @public
*/
static projectionInside(pointA, pointB, point, containsEndpoint) {
containsEndpoint = (containsEndpoint !== undefined) ? containsEndpoint : true;
if (pointA[0] === pointB[0] && pointA[1] === pointB[1]) {
if (containsEndpoint) {
return point[0] === pointA[0] && point[1] === pointA[1];
}
else {
return false;
}
}
point = MathUtils.projectPointToLineSegment(pointA, pointB, point);
if (containsEndpoint) {
return point[1] >= Math.min(pointB[1], pointA[1]) && Math.max(pointB[1], pointA[1]) >= point[1] &&
point[0] >= Math.min(pointB[0], pointA[0]) && Math.max(pointB[0], pointA[0]) >= point[0];
}
else {
return (point[1] > Math.min(pointB[1], pointA[1]) && Math.max(pointB[1], pointA[1]) > point[1] &&
point[0] > Math.min(pointB[0], pointA[0]) && Math.max(pointB[0], pointA[0]) > point[0]) ||
(point[0] === pointB[0] && pointB[0] === pointA[0] && point[1] > Math.min(pointB[1], pointA[1]) && Math.max(pointB[1], pointA[1]) > point[1]) ||
(point[1] === pointB[1] && pointB[1] === pointA[1] && point[0] > Math.min(pointB[0], pointA[0]) && Math.max(pointB[0], pointA[0]) > point[0]);
}
}
/**
* 通过线段和原点获取投影点。
* @param {Array<number>} pointA - 线段点A。
* @param {Array<number>} pointB - 线段点B。
* @param {Array<number>} point - 测试点。
* @returns {Array<number>} 投影点。
* @example
* const projectPoint = THING.MathUtils.projectPointToLineSegment([10,0],[10,10],[10,5]);
* console.log(projectPoint);
* @public
*/
static projectPointToLineSegment(pointA, pointB, point) {
let k, b;
if (Math.abs(pointB[0] - pointA[0]) <= 0.001) {
k = "no";
b = pointB[0];
}
else {
k = (pointB[1] - pointA[1]) / (pointB[0] - pointA[0]);
b = pointB[1] - k * pointB[0];
}
let calx, caly;
if (k !== "no") {
if (k !== 0) {
calx = ((point[1] + point[0] / k) - b) * k / (k * k + 1);
caly = k * calx + b;
}
else {
calx = point[0];
caly = pointB[1];
}
}
else {
calx = b;
caly = point[1];
}
return [calx, caly];
}
/**
* 通过删除冗余点和几乎共线的点,简化点数组。
* @param {Array<Array<number>>} [array=[]] 要简化的点数组。
* @param {boolean} [filterEqualpoints=true] 如果为true,删除重复的点集。
* @param {number} [deviation=1.5] 用于确定共线性的向量的角度偏差。
* @returns {Array<Array<number>>} 简化后的点数组。
* @example
* // 简化一个多边形的点集
* const polygon = [
* [0, 0, 0],
* [1, 0, 0],
* [1.01, 0, 0], // 几乎共线的点
* [2, 0, 0],
* [2, 0, 1],
* [2, 0, 1.01], // 重复的点
* [0, 0, 1]
* ];
* const simplified = THING.Math.simplifyPoints(polygon);
* // 返回简化后的点集:[[0,0,0], [2,0,0], [2,0,1], [0,0,1]]
*
* // 使用自定义参数
* const path = [
* [0, 0, 0],
* [1, 0, 0],
* [1, 0, 1],
* [1, 0, 1], // 重复点
* [0, 0, 1]
* ];
* const result = THING.Math.simplifyPoints(
* path,
* true, // 过滤重复点
* 2.0 // 更大的角度偏差
* );
*
* // 在路径优化中使用
* function optimizePath(waypoints) {
* // 移除冗余点,保持路径的基本形状
* return THING.Math.simplifyPoints(waypoints);
* }
* @public
*/
static simplifyPoints(array = [], filterEqualpoints = true, deviation = 1.5) {
if (!array.length || array.length < 3) {
return [];
}
let points = array.slice();
const pointsY = points[0][1];
filterAdjacentRepeatPoints(points);
if (filterEqualpoints) {
points = filterSamePoints(points);
}
filterPointsInline(points);
if (calculateSlope([points[points.length - 1], points[0], points[1]])) {
points.splice(0, 1);
}
if (calculateSlope([points[points.length - 2], points[points.length - 1], points[0]])) {
points.splice(points.length - 1, 1);
}
return points;
function filterSamePoints(p) {
// convert points to 2D and convert to lines
let pLines = [];
for (let i = 0; i < p.length - 1; i++) {
pLines.push([[p[i][0], p[i][2]], [p[i + 1][0], p[i + 1][2]]]);
}
pLines.push([[p[p.length - 1][0], p[p.length - 1][2]], [p[0][0], p[0][2]]]);
// the array index to be cut
let spliceIndices = [];
for (let i = 0; i < pLines.length - 1; i++) {
for (let j = i + 1; j < pLines.length; j++) {
const curLine = pLines[i];
const otherLine = pLines[j];
if (MathUtils.equalsVector(curLine[0], otherLine[1]) && MathUtils.equalsVector(curLine[1], otherLine[0])) {
spliceIndices.push([i, j]);
}
}
}
// the set of lines in the closed sum region
let closedAreaLines = [];
getclosedAreaLines(closedAreaLines, pLines, spliceIndices);
// convert lines to 2D points
let closedAreaPoints = [];
for (let i = 0; i < closedAreaLines.length; i++) {
const lPoints = [];
closedAreaLines[i].forEach(closedAreaLine => {
lPoints.push(closedAreaLine[0]);
});
closedAreaPoints.push(lPoints);
}
// get the point set with the largest area
let maxAreaPoints = [];
let maxArea = 0;
for (let i = 0; i < closedAreaPoints.length; i++) {
const curArea = Math.abs(MathUtils.getArea(closedAreaPoints[i]));
if (maxArea < curArea) {
maxArea = curArea;
maxAreaPoints = closedAreaPoints[i];
}
}
// convert to 3D points
for (let i = 0; i < maxAreaPoints.length; i++) {
const points = maxAreaPoints[i].slice();
maxAreaPoints[i] = [points[0], pointsY, points[1]];
}
return maxAreaPoints;
}
function getclosedAreaLines(cLines, lines, indices) {
if (indices.length > 1) {
for (let i = indices.length - 1; i >= 0; i--) {
const curIndex = indices[i];
for (let j = indices.length - 2; j >= 0; j--) {
const otherIndex = indices[j];
if (!isIntersect(curIndex, otherIndex)) {
cLines.push(lines.splice(curIndex[0] + 1, curIndex[1] - curIndex[0] - 1));
lines.splice(curIndex[0], 2);
indices.splice(i, 2);
for (let k = 0; k < indices.length; k++) {
if (indices[k][0] > curIndex[0]) {
indices[k][0] -= (curIndex[1] - curIndex[0] + 1);
}
if (indices[k][1] > curIndex[0]) {
indices[k][1] -= (curIndex[1] - curIndex[0] + 1);
}
}
getclosedAreaLines(cLines, lines, indices);
return;
}
}
}
}
else if (indices.length === 1) {
cLines.push(lines.splice(indices[0][0] + 1, indices[0][1] - indices[0][0] - 1));
lines.splice(indices[0][0], 2);
cLines.push(lines);
}
else {
cLines.push(lines);
}
}
function isIntersect(a, b) {
if ((b[0] > a[1] || b[0] < a[0]) && (b[1] > a[1] || b[1] < a[0])) {
return false;
}
return true;
}
function filterPointsInline(p, idx = 1) {
for (let i = idx; i < p.length - 1; i++) {
if (calculateSlope([p[i - 1], p[i], p[i + 1]])) {
p.splice(i, 1);
filterPointsInline(p, i);
return;
}
}
}
function filterAdjacentRepeatPoints(p, idx = 0) {
for (let i = idx; i < p.length; i++) {
if (i !== (p.length - 1)) {
if (MathUtils.equalsVector(p[i], p[i + 1])) {
p.splice(i, 1);
filterAdjacentRepeatPoints(p, i);
return;
}
}
else {
if (MathUtils.equalsVector(p[i], p[0])) {
p.splice(i, 1);
}
}
}
}
function calculateSlope(p) {
const prePoint = p[0];
const curPoint = p[1];
const nexPoint = p[2];
const vectorA = MathUtils.normalizeVector(MathUtils.subVector(prePoint, curPoint));
const vectorB = MathUtils.normalizeVector(MathUtils.subVector(curPoint, nexPoint));
const acosAB = MathUtils.acos(MathUtils.dotVector(vectorA, vectorB));
const angle = MathUtils.radToDeg(acosAB);
return angle < deviation;
}
}
/**
* 检查两个线段是否相交。
* 在调用此函数之前,确保两个线段不是共线的。
* @param {Array<number>} a - 线段1的起点。
* @param {Array<number>} b - 线段1的终点。
* @param {Array<number>} c - 线段2的起点。
* @param {Array<number>} d - 线段2的终点。
* @param {number} [deviation=0.001] 线段上点的偏差。
* @returns {(Array<number> | null)} 如果线段相交,则返回交点作为[x, y]数组,否则返回null。
* @example
* const intersectPoint = THING.MathUtils.intersectLineSegments([0, 0], [2, 0], [1, 0], [1, 1])
* console.log(intersectPoint);
* @public
*/
static intersectLineSegments(a, b, c, d, deviation) {
deviation = (deviation !== undefined) ? deviation : 0.001;
// for point in line segment.
if (deviation > 0) {
if (MathUtils.pointInLineSegment(a, b, c, deviation)) {
return ((c[0] == a[0] && c[1] == a[1]) || (c[0] == b[0] && c[1] == b[1])) ? null : [c[0], c[1]];
}
if (MathUtils.pointInLineSegment(a, b, d, deviation)) {
return ((d[0] == a[0] && d[1] == a[1]) || (d[0] == b[0] && d[1] == b[1])) ? null : [d[0], d[1]];
}
if (MathUtils.pointInLineSegment(c, d, a, deviation)) {
return ((a[0] == c[0] && a[1] == c[1]) || (a[0] == d[0] && a[1] == d[1])) ? null : [a[0], a[1]];
}
if (MathUtils.pointInLineSegment(c, d, b, deviation)) {
return ((b[0] == c[0] && b[1] == c[1]) || (b[0] == d[0] && b[1] == d[1])) ? null : [b[0], b[1]];
}
}
let area_abc = (a[0] - c[0]) * (b[1] - c[1]) - (a[1] - c[1]) * (b[0] - c[0]);
let area_abd = (a[0] - d[0]) * (b[1] - d[1]) - (a[1] - d[1]) * (b[0] - d[0]);
if (area_abc * area_abd > 0) {
return null;
}
let area_cda = (c[0] - a[0]) * (d[1] - a[1]) - (c[1] - a[1]) * (d[0] - a[0]);
let area_cdb = area_cda + area_abc - area_abd;
if (area_cda * area_cdb > 0) {
return null;
}
if (area_abd - area_abc === 0) {
return null;
}
let t = area_cda / (area_abd - area_abc);
let dx = t * (b[0] - a[0]),
dy = t * (b[1] - a[1]);
return [a[0] + dx, a[1] + dy];
}
/**
* @description 通过输入已知点位数组,根据二次贝塞尔规则,生成平滑曲线的点位数组
* @param {Array} path 必选参数,输入点位数组
* @param {number} curvature 平滑范围,表示平滑位置与折线的关系,默认0.5,取值范围(0,1.0)
* @param {number} divisions 切割份数,表示每段平滑曲线分割的份数。默认50, 取值为大于0的整数
* @param {string} mode 类型,表示平滑曲线的过渡类型,默认为THING.BezierMode.Pursuit;取值为THING.BezierMode枚举。
* @returns {Array} 返回平滑曲线的点位数组
* @example
* let path1 = [[0,10,0],[0,10,50],[50,10,50],[50,10,0],[0,10,0]];
* let path2 = THING.MathUtils.genSmoothPathByBezier(path1, 0.2, 100, THING.BezierMode.Symmetry);
* // var line1 = new THING.PixelLine({ selfPoints: path1, style: {color: 'red'} });
* // var line2 = new THING.PixelLine({ selfPoints: path2, style: {color: 'blue'} });
* let camera = THING.App.current.camera;
* camera.control.enable = false;
* camera.movePath(path2, {
* time: 30000,
* loopType: 'pingpong'
* })
* @public
*/
static genSmoothPathByBezier(path, curvature = 0.5, divisions = 50, mode = THING.BezierMode.Pursuit) {
if (!path || path.length < 3) {
console.error("there must be at least 3 points in the path!");
return null;
}
if (curvature > 1.0 || curvature < 0.0) {
console.error("curvature must between 0.0 and 1.0!");
return path;
}
if (divisions <= 0) {
console.error("divisions must above 0!");
return path;
}
const generator = new BezierPathGenerator(divisions, mode);
let near = 1 - curvature;
const splitArr = generator.splitPoints(path, near);
splitArr.forEach((item, index) => {
// 将二阶贝塞尔曲线的起点、控制点和终点分割为多个点
const points = generator.getPoints(item[1], item[2], item[3]);
// 如果是路线开始和结束,则将起点和终点补全
if (index == 0 && index == splitArr.length - 1) {
splitArr[index] = [item[0], ...points, item[4]];
}
// 如果是路线开始,则在前面补全起点
else if (index == 0) {
splitArr[index] = [item[0], ...points];
}
// 如果是路线结束,则在后面补全终点
else if (index == splitArr.length - 1) {
splitArr[index] = [...points, item[4]];
}
// 否则,直接将分割后的点数组赋值给当前项
else {
splitArr[index] = [...points];
}
});
const arr = [];
splitArr.forEach((item) => {
item.forEach((itm) => {
arr.push(itm);
});
});
const anglePoint = generator.getNearPoint(arr[0], arr[1], 0.01);
arr.splice(1, 0, [anglePoint[0], anglePoint[1], anglePoint[2]]);
return arr;
}
/**
* 根据物体包围盒和角度计算位置。
* 本函数根据物体的包围盒和给定的水平和垂直角度计算位置。
* 位置是通过将从角度导出的方向向量按给定的半径缩放并加到原点上来计算的。
* 原点是包围盒中心投影到水平平面上的点。
* @param {THING.Object3D} object 物体。
* @param {number} horzAngle 水平角度。
* @param {number} vertAngle 垂直角度。
* @param {THING.Box3} boundingBox 包围盒。
* @param {number} radius 半径。
* @returns {Array<number>} 计算的位置作为三个数字的数组,分别代表x、y和z坐标。
* @example
* // 根据物体包围盒和角度计算位置
* const object = new THING.Box(); // 一个3D对象
* const horzAngle = 45; // 水平角度45度
* const vertAngle = 30; // 垂直角度30度
* const position = THING.Math.getPositionByBoundingBoxAngles(
* object,
* horzAngle,
* vertAngle
* );
*
* // 使用自定义包围盒和半径
* const customBox = {
* min: [0, 0, 0],
* max: [10, 10, 10],
* center: [5, 5, 5],
* radius: 8.66 // √(5² + 5² + 5²)
* };
* const customRadius = 20;
* const pos = THING.Math.getPositionByBoundingBoxAngles(
* object,
* 0, // 正面视角
* 45, // 45度俯视角
* customRadius,
* customBox
* );
* @public
*/
static getPositionByBoundingBoxAngles(object, horzAngle, vertAngle, boundingBox, radius) {
if (!boundingBox) {
boundingBox = object.boundingBox;
}
if (!radius) {
radius = boundingBox.radius;
}
const worldCenter = boundingBox.center;
const origin = [worldCenter[0], boundingBox.min[1], worldCenter[2]];
const selfCenter = MathUtils.subVector(boundingBox.center, origin);
let direction = MathUtils.normalizeVector(MathUtils.getDirectionFromAngles(horzAngle, vertAngle));
let offsetPos = MathUtils.addVector(selfCenter, MathUtils.scaleVector(direction, radius * 2));
return MathUtils.addVector(origin, offsetPos);
}
/**
* 根据位置、半径和密度计算圆路径。
* 这个函数生成一系列点,形成一个圆路径。
* 根据密度参数,这些点沿圆均匀分布。
* @param {Array<number>} position 圆的位置,作为一个包含三个数字的数组,分别代表x, y, z坐标。
* @param {number} radius 圆的半径。
* @param {number} density 圆的密度,决定了沿圆生成多少点。密度越高,生成的点越多。
* @returns {Array<Array<number>>} 形成圆路径的一系列点。每个点作为一个包含三个数字的数组,分别代表x, y, z坐标。
* @example
* // Generate a circle path
* const center = [0, 0, 0]; // Circle center position
* const radius = 10; // Circle radius
* const density = 36; // Density, resulting in a point every 10 degrees
* const circlePath = THING.Math.genCirclePath(center, radius, density);
*
* // Create a patrol path
* const patrolCenter = [100, 0, 100];
* const patrolRadius = 50;
* const pathPoints = THING.Math.genCirclePath(
* patrolCenter,
* patrolRadius,
* 24 // Density, resulting in a point every 15 degrees
* );
*
* // Use in animation
* function createCircularAnimation(object, center, radius) {
* const path = THING.Math.genCirclePath(center, radius);
* // object.movePath({
* // path: path,
* // time: 5000,
* // loop: true
* // });
* }
* @public
*/
static genCirclePath(position, radius, density = 18) {
if (!position || !radius) {
return [];
}
const path = [];
for (let i = 0; i < density; i++) {
const angle = i / density * (2 * MathUtils.PI);
const x = position[0] + radius * MathUtils.cos(angle);
const z = position[2] + radius * MathUtils.sin(angle);
path.push([x, position[1], z]);
}
return path;
}
/**
* 检查点是否在圆内。
* @param {Array<number>} point 检查的点,作为一个包含三个数字的数组,分别代表x, y, z坐标。
* @param {Array<number>} center 圆心位置,作为一个包含三个数字的数组,分别代表x, y, z坐标。
* @param {number} radius 圆的半径。
* @returns {boolean} 如果点在圆内,则返回true,否则返回false。
* @example
* // 检查点是否在圆内
* const point = [10, 0, 10]; // 检查的点
* const center = [0, 0, 0]; // 圆心位置
* const radius = 10; // 圆的半径
* const isInside = MathUtils.containsPoint(point, center, radius);
* console.log(isInside); // 输出:true
* @public
*/
static containsPoint(point, center, radius) {
let length = MathUtils.getDistance(point, center);
if (length <= radius) {
return true;
}
else {
return false;
}
}
/**
* 检查球是否在盒子里。
* @param {Array<number>} min 盒子的最小值。
* @param {Array<number>} max 盒子的最大值。
* @param {Array<number>} center 球的中心。
* @param {number} radius 球的半径。
* @returns {boolean} 如果球在盒子里,则返回true,否则返回false。
* @example
* // 检查球是否在盒子里
* const min = [0, 0, 0]; // 盒子的最小值
* const max = [10, 10, 10]; // 盒子的最大值
* const center = [5, 5, 5]; // 球的中心
* const radius = 5; // 球的半径
* const isInside = MathUtils.isCollisionBoxAndSphere(min, max, center, radius);
* console.log(isInside); // 输出:true
* @public
*/
static IsCollisionBoxAndSphere(min, max, center, radius) {
let x = center[0];
x = x > max[0] ? max[0] : x;
x = x < min[0] ? min[0] : x;
let y = center[1];
y = y > max[1] ? max[1] : y;
y = y < min[1] ? min[1] : y;
let z = center[2];
z = z > max[2] ? max[2] : z;
z = z < min[2] ? min[2] : z;
// The point on the box closest to the center of the ball
let nearestPoint = [x, y, z];
let distance = MathUtils.getDistance(nearestPoint, center);
return distance <= radius;
}
/**
* 渐变配置。
* @typedef {object} gradient 渐变对象。
* @property {number} position 颜色在此位置。
*/
/**
* 获取渐变颜色像素数据。
* @param {object} gradient 渐变对象。
* @param {number} steps 渐变步骤。
* @param {Uint8Array} target 目标像素数据。
* @returns {Uint8Array} 返回渐变颜色的像素数据。
* @example
* var gradient = {
* 0: '#000000',
* 1: '#ffffff'
* };
* var steps = 10;
* var target = new Uint8Array(steps * 4);
* var pixelData = THING.Math.getGradientPixelData(gradient, steps, target);
* console.log(pixelData);
* @public
*/
static getGradientPixelData(gradient, steps, target = new Uint8Array(steps * 4)) {
const colorStops = [];
for (let i in gradient) {
const colorStop = {
position: parseFloat(i), // string will significantly slow down the speed
color: Utils.parseColor(gradient[i])
}
colorStops.push(colorStop);
}
colorStops.sort((a, b) => a.position - b.position);
const prevColor = [];
prevColor[0] = colorStops[0].color[0];
prevColor[1] = colorStops[0].color[1];
prevColor[2] = colorStops[0].color[2];
const color = [];
let prevPosition = 0, colorStopIndex = 0;
for (let i = 0; i < steps; i++) {
const position = i / (steps - 1);
if (position > 0) {
while (colorStopIndex < colorStops.length) {
const colorStop = colorStops[colorStopIndex];
if (colorStop.position < position) {
prevColor[0] = colorStop.color[0];
prevColor[1] = colorStop.color[1];
prevColor[2] = colorStop.color[2];
color[0] = prevColor[0];
color[1] = prevColor[1];
color[2] = prevColor[2];
prevPosition = colorStop.position;
}
else {
const t = (position - prevPosition) / (colorStop.position - prevPosition);
color[0] = t * (colorStop.color[0] - prevColor[0]) + prevColor[0];
color[1] = t * (colorStop.color[1] - prevColor[1]) + prevColor[1];
color[2] = t * (colorStop.color[2] - prevColor[2]) + prevColor[2];
break;
}
colorStopIndex++;
}
}
else {
color[0] = prevColor[0];
color[1] = prevColor[1];
color[2] = prevColor[2];
}
const offset = i * 4;
target[offset] = color[0] * 255;
target[offset + 1] = color[1] * 255;
target[offset + 2] = color[2] * 255;
target[offset + 3] = 255;
}
return target;
}
/**
* 生成温度数据的3D纹理。(IDW插值算法)
* @param {object} pointsData 温度点数据对象
* @param {Array<Array<number>>} pointsData.points 温度点数组,每个点包含[x,y,z,温度值]
* @param {object} pointsData.obb 定向包围盒数据
* @param {Array<number>} pointsData.obb.center 包围盒中心点坐标[x,y,z]
* @param {Array<number>} pointsData.obb.size 包围盒大小[width,height,depth]
* @param {Array<number>} pointsData.obb.angles 包围盒旋转角度[x,y,z]
* @param {object} options 配置选项
* @param {Array<number>} [options.textureSize=[128,128,128]] 纹理尺寸[u,v,2]
* @param {number} [options.indoorTemperature=20] 室内基础温度
* @param {number} [options.decayExponent=2] 温度衰减指数
* @param {number} [options.minDistance=0.01] 最小距离限制
* @param {Array<number>} [options.influenceRadiusFactors=[0.3,0.3,0.3]] 影响半径系数[u,v,w]
* @param {Uint8Array} [options.target] 目标数据数组,如果不提供则创建新数组
* @param {number} [options.minValue] 最小温度值限制
* @param {number} [options.maxValue] 最大温度值限制
* @returns {object} 返回生成的温度数据
* @property {Uint8Array} data 生成的温度数据纹理
* @property {Array<number>} size 纹理尺寸[width,height,depth]
* @example
* // 生成温度数据纹理
* const pointsData = {
* points: [
* [0, 0, 0, 25], // x,y,z,温度
* [10, 0, 0, 30],
* [0, 10, 0, 28]
* ],
* obb: {
* center: [5, 5, 0],
* size: [20, 20, 1],
* angles: [0, 0, 0]
* }
* };
*
* const options = {
* textureSize: [64, 64, 1],
* indoorTemperature: 22,
* decayExponent: 1.5
* };
*
* const result = THING.Math.generaTemperatureData(pointsData, options);
* // result.data 包含了温度分布数据
* // result.size 包含了纹理尺寸
* @public
*/
static generaTemperatureData(pointsData, options) {
const textureSize = options.textureSize || [128, 128, 128];
const [x1, y1, z1] = textureSize;
const indoorTemperature = options.indoorTemperature !== undefined ? options.indoorTemperature : 20;
const decayExponent = options.decayExponent !== undefined ? options.decayExponent : 2;
const minDistance = options.minDistance !== undefined ? options.minDistance : 0.01;
const influenceRadiusFactors = options.influenceRadiusFactors || [0.3, 0.3, 0.3];
const dataUint = options.target || new Uint8Array(x1 * y1 * z1);
const min = options.minValue !== undefined ? options.minValue : null;
const max = options.maxValue !== undefined ? options.maxValue : null;
const indoorTemperatureRadius = options.indoorTemperatureRadius !== undefined ? options.indoorTemperatureRadius : -1;
const points = pointsData.points;
const obb = pointsData.obb;
const center = obb.center;
const size = obb.size;
const angles = obb.angles;
_vec3_0[0] = center[0];
_vec3_0[1] = center[1];
_vec3_0[2] = center[2];
_vec3_1[0] = size[0];
_vec3_1[1] = size[1];
_vec3_1[2] = size[2];
_vec3_2[0] = angles[0];
_vec3_2[1] = angles[1];
_vec3_2[2] = angles[2];
MathUtils.getQuatFromAngles(_vec3_2, _quat);
mat4.fromRotationTranslationScale(_mat4_1, _quat, _vec3_0, _vec3_1);
// 创建逆矩阵用于世界坐标到局部坐标的转换
const inverseMatrix = mat4.invert(_mat4_2, _mat4_1);
let minValue = Infinity;
let maxValue = -Infinity;
// 使用Float32Array提高精度和性能
const data = new Float32Array(x1 * y1 * z1).fill(0);
const dataCount = new Float32Array(x1 * y1 * z1);
if(indoorTemperatureRadius > 0){
const weight = 1.0 / Math.pow(indoorTemperatureRadius, decayExponent);
dataCount.fill(weight);
data.fill(weight * indoorTemperature);
}
// 计算影响半径,用于减少计算范围,提高性能
const influenceRadius = [
textureSize[0] * influenceRadiusFactors[0],
textureSize[1] * influenceRadiusFactors[1],
textureSize[2] * influenceRadiusFactors[2]
];
for (let i = 0; i < points.length; i++) {
const point = points[i];
const [px, py, pz, value] = point;
// 统计最小最大值
if (value < minValue) minValue = value;
if (value > maxValue) maxValue = value;
const tpx = _vec3_0[0] = px;
const tpy = _vec3_0[1] = py;
const tpz = _vec3_0[2] = pz;
// 世界坐标转局部坐标
vec3.transformMat4(_vec3_1, _vec3_0, inverseMatrix);
// 计算当前点在纹理空间中的位置
const [worldX, worldY, worldZ] = _localToTextureCoord(
_vec3_0,
_vec3_1,
textureSize,
);
// 计算影响范围边界
const xStart = Math.max(0, Math.floor(worldX - influenceRadius[0]));
const xEnd = Math.min(x1, Math.floor(worldX + influenceRadius[0]));
const yStart = Math.max(0, Math.floor(worldY - influenceRadius[1]));
const yEnd = Math.min(y1, Math.floor(worldY + influenceRadius[1]));
const zStart = Math.max(0, Math.floor(worldZ - influenceRadius[2]));
const zEnd = Math.min(z1, Math.floor(worldZ + influenceRadius[2]));
for (let z = zStart; z < zEnd; z++) {
for (let y = yStart; y < yEnd; y++) {
for (let x = xStart; x < xEnd; x++) {
_vec3_3[0] = x;
_vec3_3[1] = y;
_vec3_3[2] = z;
_textureCoordToLocal(_vec3_2, _vec3_3, textureSize);
const [dx1, dy1, dz1] = vec3.transformMat4(_vec3_4, _vec3_2, _mat4_1);
const dx = tpx - dx1;
const dy = tpy - dy1;
const dz = tpz - dz1;
// 计算距离并应用最小距离限制
let distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
distance = Math.max(distance, minDistance);
// 计算权重
const weight = 1.0 / Math.pow(distance, decayExponent);
const index = x + y * x1 + z * x1 * y1;
dataCount[index] += weight;
data[index] += value * weight;
}
}
}
}
// 计算最小值和最大值
if (min !== null) minValue = min;
if (max !== null) maxValue = max;
// 归一化处理并转换为Uint8
for (let i = 0; i < data.length; i++) {
let finalValue;
if (dataCount[i] > 0) {
finalValue = data[i] / dataCount[i];
}
else {
finalValue = indoorTemperature; // 直接使用室内温度
}
finalValue = Math.max(minValue, Math.min(maxValue, finalValue));
// 添加值域限制和归一化
const normalized = Math.min(1, Math.max(0, (finalValue - minValue) / (maxValue - minValue)));
if (isNaN(normalized)) { // 处理 maxValue === minValue 的情况
dataUint[i] = 0; // 或者根据需要设置为 0 或 127/128
}
else {
dataUint[i] = Math.round(normalized * 255); // 使用 255 更充分利用范围
}
}
return {
data: dataUint,
size: textureSize
};
}
/**
* 生成热力图数据纹理。
* @param {object} pointsData 热力点数据对象。
* @param {Array<Array<number>>} pointsData.points 热力点数组,每个点包含[x,y,z,热力值]。
* @param {object} pointsData.obb 包围盒数据。
* @param {Array<number>} pointsData.obb.center 包围盒中心点。
* @param {Array<number>} pointsData.obb.size 包围盒大小。
* @param {Array<number>} pointsData.obb.angles 包围盒旋转角度。
* @param {object} options 配置选项。
* @param {Array<number>} [options.textureSize=[128,128,128]] 纹理尺寸。
* @param {number} [options.minDistance=0.01] 最小距离限制。
* @param {number} [options.radius=1] 热力影响半径。
* @param {number} [options.minValue] 最小热力值。
* @param {number} [options.maxValue] 最大热力值。
* @param {Uint8Array} [options.target] 目标数据数组。
* @returns {object} 返回生成的热力图数据纹理对象。
* @property {Uint8Array} data 生成的热力图数据纹理。
* @property {Array<number>} size 生成的纹理尺寸。
* @example
* // 生成热力图数据
* const pointsData = {
* points: [
* [0, 0, 0, 100], // 位置和热力值
* [10, 0, 0, 80],
* [0, 10, 0, 90]
* ],
* obb: {
* center: [5, 5, 5],
* size: [20, 20, 20],
* angles: [0, 0, 0]
* }
* };
*
* const options = {
* textureSize: [64, 64, 64],
* radius: 2,
* minDistance: 0.1
* };
*
* const heatData = THING.Math.generateHeatData(pointsData, options);
* console.log(heatData);
* // heatData.data 包含了热力图的数据
* // heatData.size 包含了纹理的尺寸
* @public
*/
static generateHeatData(pointsData, options) {
const textureSize = options.textureSize || [128, 128, 128];
const [x1, y1, z1] = textureSize;
const minDistance = options.minDistance || 0.01;
const radius = options.radius || 1;
const dataUint = options.target || new Uint8Array(x1 * y1 * z1);
const min = options.minValue !== undefined ? options.minValue : null;
const max = options.maxValue !== undefined ? options.maxValue : null;
if (min === null || max === null) {
console.warn('minValue and maxValue must be set');
return null;
}
const points = pointsData.points;
const obb = pointsData.obb;
const center = obb.center;
const size = obb.size;
const angles = obb.angles;
_vec3_0[0] = center[0];
_vec3_0[1] = center[1];
_vec3_0[2] = center[2];
_vec3_1[0] = size[0];
_vec3_1[1] = size[1];
_vec3_1[2] = size[2];
_vec3_2[0] = angles[0];
_vec3_2[1] = angles[1];
_vec3_2[2] = angles[2];
MathUtils.getQuatFromAngles(_vec3_2, _quat);
mat4.fromRotationTranslationScale(_mat4_1, _quat, _vec3_0, _vec3_1);
// 创建逆矩阵用于世界坐标到局部坐标的转换
const inverseMatrix = mat4.invert(_mat4_2, _mat4_1);
// 改为Float32Array提高性能
const data = new Float32Array(x1 * y1 * z1).fill(0);
let minValue = Infinity;
let maxValue = -Infinity;
const sizeXY = x1 * y1;
for (let i = 0; i < points.length; i++) {
const point = points[i];
const [px, py, pz, value] = point;
// 统计最小最大值
if (value < minValue) minValue = value;
if (value > maxValue) maxValue = value;
const tpx = _vec3_0[0] = px;
const tpy = _vec3_0[1] = py;
const tpz = _vec3_0[2] = pz;
// 世界坐标转局部坐标
vec3.transformMat4(_vec3_1, _vec3_0, inverseMatrix);
const [px1, py1, pz1] = _localToTextureCoord(_vec3_0, _vec3_1, textureSize);
// 计算影响范围边界
const xStart = Math.max(0, Math.floor(px1 - radius / size[0] * x1));
const xEnd = Math.min(x1, Math.floor(px1 + radius / size[0] * x1));
const yStart = Math.max(0, Math.floor(py1 - radius / size[2] * y1));
const yEnd = Math.min(y1, Math.floor(py1 + radius / size[2] * y1));
const zStart = Math.max(0, Math.floor(pz1 - radius / size[1] * z1));
const zEnd = Math.min(z1, Math.floor(pz1 + radius / size[1] * z1));
for (let z = zStart; z < zEnd; z++) {
for (let y = yStart; y < yEnd; y++) {
for (let x = xStart; x < xEnd; x++) {
_vec3_3[0] = x;
_vec3_3[1] = y;
_vec3_3[2] = z;
_textureCoordToLocal(_vec3_2, _vec3_3, textureSize);
const [dx1, dy1, dz1] = vec3.transformMat4(_vec3_4, _vec3_2, _mat4_1);
const dx = tpx - dx1;
const dy = tpy - dy1;
const dz = tpz - dz1;
const value1 = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (value1 > radius|| value1 < minDistance ) continue; // 添加最小距离限制
const alpha = 1 - value1 /radius;
const index = x + y * x1 + z * sizeXY;
// 更安全的数值处理
const newValue = data[index] + value * alpha;
data[index] = Math.min(newValue, max);
}
}
}
}
if (min !== null) minValue = min;
if (max !== null) maxValue = max;
// 添加归一化处理
for (let i = 0; i < data.length; i++) {
const normalized = Math.min(1, Math.max(0, (data[i] - minValue) / (maxValue - minValue)));
dataUint[i] = Math.round(normalized * 254);
}
return {
data: dataUint,
size: textureSize
};
}
}
export { MathUtils };