UNPKG

@js-basics/vector

Version:

A 3D Vector lib including arithmetic operator overloading (+ - * / % **).

356 lines (295 loc) 7.46 kB
import { FORWARD, LEFT, RIGHT, UP } from './vector.js'; import { isArray, multQuatVec, isNumber } from './utils/math.js'; import { cachedFunction, defineMatrixLength, cachedValueOf } from './operator.js'; import { degree, isAngle } from './degree.js'; import { convertToCSSVars } from './utils/css.js'; const X = 0; const Y = 1; const Z = 2; const W = 3; const AXES = Symbol('axes'); const FORWARD_CACHE = Symbol('forward cache'); const LEFT_CACHE = Symbol('left cache'); const UP_CACHE = Symbol('up cache'); const INVERSE_CACHE = Symbol('inverse cache'); function length([x, y, z, w]) { return Math.sqrt(x * x + y * y + z * z + w * w); } function normalize(axes) { const len = length(axes); axes[X] /= len; axes[Y] /= len; axes[Z] /= len; axes[W] /= len; } function look(forward, up) { const vector = forward.normalize(); const vector2 = up.crossNormalize(vector); const vector3 = vector.crossNormalize(vector2); const m00 = vector2.x; const m01 = vector2.y; const m02 = vector2.z; const m10 = vector3.x; const m11 = vector3.y; const m12 = vector3.z; const m20 = vector.x; const m21 = vector.y; const m22 = vector.z; const num8 = m00 + m11 + m22; const quat = new Array(4); if (num8 > 0) { let num = Math.sqrt(num8 + 1); quat[W] = num * 0.5; num = 0.5 / num; quat[X] = (m12 - m21) * num; quat[Y] = (m20 - m02) * num; quat[Z] = (m01 - m10) * num; return quat; } if (m00 >= m11 && m00 >= m22) { const num7 = Math.sqrt(1 + m00 - m11 - m22); const num4 = 0.5 / num7; quat[X] = 0.5 * num7; quat[Y] = (m01 + m10) * num4; quat[Z] = (m02 + m20) * num4; quat[W] = (m12 - m21) * num4; return quat; } if (m11 > m22) { const num6 = Math.sqrt(1 + m11 - m00 - m22); const num3 = 0.5 / num6; quat[X] = (m10 + m01) * num3; quat[Y] = 0.5 * num6; quat[Z] = (m21 + m12) * num3; quat[W] = (m20 - m02) * num3; return quat; } const num5 = Math.sqrt(1 + m22 - m00 - m11); const num2 = 0.5 / num5; quat[X] = (m20 + m02) * num2; quat[Y] = (m21 + m12) * num2; quat[Z] = 0.5 * num5; quat[W] = (m01 - m10) * num2; return quat; } function axisAngle(axis, angle) { const quat = new Array(4); const a = angle * 0.5; const sa = Math.sin(a); const ca = Math.cos(a); quat[X] = sa * axis.x; quat[Y] = sa * axis.y; quat[Z] = sa * axis.z; quat[W] = ca; return quat; } function getQuat(x, y, z, w) { if (isNumber(x)) { return [x, y, z, w]; } if (isArray(x)) { return [...x]; } if (isAngle(y)) { return axisAngle(x, y); } if (x && y) { return look(x, y); } return undefined; } function from(x, y, z, w) { if (x && isNumber(x.w)) { return getQuat(x.x, x.y, x.z, x.w); } return getQuat(x, y, z, w) || [0, 0, 0, 1]; } class AQuaternion { constructor(x, y, z, w) { this[AXES] = from(x, y, z, w); normalize(this[AXES]); } // eslint-disable-next-line no-unused-vars set(x, y, z, w) { throw new Error('set x() not implemented'); } multiply(other, y, z, w) { if (isNumber(other.w)) { return this.multiplyQuaternion(other); } const o = getQuat(other, y, z, w); if (o) { return this.multiplyQuaternion(new this.constructor(o)); } return this.multiplyVector(other); } multiplyVector(vec) { return multQuatVec(this, vec); } multiplyQuaternion(quat) { const q1x = this.x; const q1y = this.y; const q1z = this.z; const q1w = this.w; const q2x = quat.x; const q2y = quat.y; const q2z = quat.z; const q2w = quat.w; const x = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y; const y = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z; const z = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x; const w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z; return new this.constructor(x, y, z, w); } mul(other, y, z, w) { return this.multiply(other, y, z, w); } get inverse() { const { x, y, z, w } = this; return this.constructor(x * -1, y * -1, z * -1, w); } get inv() { return this.inverse; } equals(v) { return this.x === v.x && this.y === v.y && this.z === v.z && this.w === v.w; } get left() { return this.multiplyVector(LEFT); } get dir() { return this.multiplyVector(FORWARD); } get up() { return this.multiplyVector(UP); } get [0]() { return this.left; } get [1]() { return this.dir; } get [2]() { return this.up; } get x() { return this[AXES][X]; } set x(_) { throw new Error('set x() not implemented'); } get y() { return this[AXES][Y]; } set y(_) { throw new Error('set y() not implemented'); } get z() { return this[AXES][Z]; } set z(_) { throw new Error('set z() not implemented'); } get w() { return this[AXES][W]; } set w(_) { throw new Error('set w() not implemented'); } toJSON() { const { x, y, z, w } = this; return { x, y, z, w, a1: 1 - y * y * 2 - z * z * 2, a2: x * y * 2 - z * w * 2, a3: x * z * 2 + y * w * 2, b1: x * y * 2 + z * w * 2, b2: 1 - x * x * 2 - z * z * 2, b3: y * z * 2 - x * w * 2, c1: x * z * 2 - y * w * 2, c2: y * z * 2 + x * w * 2, c3: 1 - x * x * 2 - y * y * 2 }; } toString() { return JSON.stringify(this.toJSON()); } toCSSVars(name, target) { return convertToCSSVars(name, this.toJSON(), target); } } cachedValueOf(AQuaternion); defineMatrixLength(AQuaternion); export class Quaternion extends AQuaternion { set(x, y, z, w) { if (x instanceof AQuaternion) { this[AXES] = [...x[AXES]]; } else { this[AXES] = from(x, y, z, w); normalize(this[AXES]); } } set x(x) { this[AXES][X] = x; } set y(y) { this[AXES][Y] = y; } set z(z) { this[AXES][Z] = z; } set w(w) { this[AXES][W] = w; } get x() { return this[AXES][X]; } get y() { return this[AXES][Y]; } get z() { return this[AXES][Z]; } get w() { return this[AXES][W]; } } function fromCache(scope, key, fn) { let res = scope[key]; if (!res) { res = fn(); scope[key] = res; } return res; } export class IQuaternion extends AQuaternion { get left() { return fromCache(this, LEFT_CACHE, () => this.multiplyVector(LEFT)); } get dir() { return fromCache(this, FORWARD_CACHE, () => this.multiplyVector(FORWARD)); } get up() { return fromCache(this, UP_CACHE, () => this.multiplyVector(UP)); } get inverse() { return fromCache(this, INVERSE_CACHE, () => { const { x, y, z, w } = this; return this.constructor(x * -1, y * -1, z * -1, w); }); } } const quaternionFactory = cachedFunction((x, y, z, w) => new Quaternion(x, y, z, w)); export const quaternion = (x, y, z, w) => quaternionFactory(x, y, z, w); const iquaternionFactory = cachedFunction((x, y, z, w) => new IQuaternion(x, y, z, w)); export const iquaternion = (x, y, z, w) => iquaternionFactory(x, y, z, w); const LEFT90 = new IQuaternion(LEFT, degree(90)); export function fromOrientation({ alpha, beta, gamma }, orientation) { let rot = iquaternion(UP, degree(alpha)).mul(RIGHT, degree(beta)).mul(FORWARD, degree(gamma)).mul(LEFT90); rot = iquaternion(rot.dir, degree(orientation)).mul(rot); return rot; } export const IDENTITY = iquaternion(0, 0, 0, 1);