1450 lines · 38.7 KB · JavaScript
RawDownload
// utilMath.js// Created by Ada <ada@thingvellir.net> on 2025-12-06// Copyright 2025 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//// SPDX-License-Identifier: Apache-2.0"use strict"; /** @type {number} */const DEGREES_TO_RADIANS = Math.PI / 180; /** @type {number} */const RADIANS_TO_DEGREES = 180 / Math.PI; /** * @param {number} from * @param {number} to * @param {number} t * @returns {number} */function lerp(from, to, t) {    return from + t * (to - from);} /** * @param {number} value * @param {number} min * @param {number} max * @returns {number} */function clamp(value, min, max) {    return Math.max(min, Math.min(max, value));} /** * Rounds a `Number` to N fractional digits. * Similar to `Number.toFixed`, but without stringifying. * @param {number} number * @param {number} digits - Fractional decimal digits * @returns {number} */function quantize(number, digits) {    const scale = 10 ** digits;    return Math.round(number * scale) / scale;} /** * @param {number} a * @param {number} b * @param {number} [epsilon=1e-5] * @returns {boolean} `true` if `a` is roughly equal to `b`, * within the range specified by `epsilon`, otherwise `false`. */function withinEpsilon(a, b, epsilon = 1e-5) {    return Math.abs(a - b) <= epsilon;} /** * Converts a human-readable duration into milliseconds. * @param {Object} duration * @param {number} [duration.seconds=0] * @param {number} [duration.minutes=0] * @param {number} [duration.hours=0] * @param {number} [duration.weeks=0] * @param {number} [duration.months=0] * @param {number} [duration.years=0] * @returns {number} */function durationMillis({    seconds = 0,    minutes = 0,    hours = 0,    days = 0,    weeks = 0,    months = 0,    years = 0,}) {    const MSEC_PER_SEC = 1000;    const MSEC_PER_MIN = MSEC_PER_SEC * 60;    const MSEC_PER_HOUR = MSEC_PER_MIN * 60;    const MSEC_PER_DAY = MSEC_PER_HOUR * 24;    const MSEC_PER_WEEK = MSEC_PER_DAY * 7;    const MSEC_PER_MONTH = MSEC_PER_DAY * 31;    const MSEC_PER_YEAR = MSEC_PER_MONTH * 12;     let duration = 0;    duration += seconds * MSEC_PER_SEC;    duration += minutes * MSEC_PER_MIN;    duration += hours * MSEC_PER_HOUR;    duration += days * MSEC_PER_DAY;    duration += weeks * MSEC_PER_WEEK;    duration += months * MSEC_PER_MONTH;    duration += years * MSEC_PER_YEAR;     return duration;} /** * A scalar 3-vector. Used for representing positions and Euler angles. * * Compatible with the existing {@link Vec3} type. */class Vector3 {    /**     * `Vector3(0, 0, 0)`     * @returns {Vector3}     */    static get ZERO() { return new Vector3(0, 0, 0); }    /**     * `Vector3(0, 1, 0)`     * @returns {Vector3}     */    static get UP() { return new Vector3(0, 1, 0); }    /**     * `Vector3(0, -1, 0)`     * @returns {Vector3}     */    static get DOWN() { return new Vector3(0, -1, 0); }    /**     * `Vector3(1, 0, 0)`     * @returns {Vector3}     */    static get RIGHT() { return new Vector3(1, 0, 0); }    /**     * `Vector3(-1, 0, 0)`     * @returns {Vector3}     */    static get LEFT() { return new Vector3(-1, 0, 0); }    /**     * `Vector3(0, 0, -1)`     * @returns {Vector3}     */    static get FORWARD() { return new Vector3(0, 0, -1); }    /**     * `Vector3(0, 0, 1)`     * @returns {Vector3}     */    static get BACKWARD() { return new Vector3(0, 0, 1); }     /** @type {number} */ x;    /** @type {number} */ y;    /** @type {number} */ z;     /**     * Alias of `x`     * @returns {number}     */    get [0]() { return this.x; }    /**     * Alias of `y`     * @returns {number}     */    get [1]() { return this.y; }    /**     * Alias of `z`     * @returns {number}     */    get [2]() { return this.z; }     *[Symbol.iterator]() { yield this.x; yield this.y; yield this.z; }     /** @returns {string} */    toString() { return `Vector3(${this.x}, ${this.y}, ${this.z})`; }     /**     * @overload     * @param {number} [x=0]     * @param {number} [y=0]     * @param {number} [z=0]     */    /**     * @overload     * @param {Object|Vector3} vec     * @param {number} [vec.x=0]     * @param {number} [vec.y=0]     * @param {number} [vec.z=0]     */    /**     * @overload     * @param {number[]} [xyz=[0, 0, 0]]     */    constructor(...args) {        if (args[0] instanceof Vector3 || typeof(args[0]) === "object") {            const { x = 0, y = 0, z = 0 } = args[0];             this.x = x;            this.y = y;            this.z = z;        } else if (args[0] instanceof Array) {            const [ x = 0, y = 0, z = 0 ] = args[0];             this.x = x;            this.y = y;            this.z = z;        } else {            const [ x = 0, y = 0, z = 0 ] = args;             this.x = x;            this.y = y;            this.z = z;        }    }     /**     * @param {Vector3} rhs     * @returns {Vector3} `this` + `rhs`     */    add(rhs) {        return new Vector3(this.x + rhs.x, this.y + rhs.y, this.z + rhs.z);    }     /**     * @param {Vector3} rhs     * @returns {Vector3} `this` - `rhs`     */    subtract(rhs) {        return new Vector3(this.x - rhs.x, this.y - rhs.y, this.z - rhs.z);    }     /**     * @param {Vector3|number} rhs     * @returns {Vector3} `this` * `rhs`     */    multiply(rhs) {        if (typeof(rhs) === "number") {            return new Vector3(this.x * rhs, this.y * rhs, this.z * rhs);        } else if (rhs instanceof Quaternion) {            // The convention is for Quat * Vec,            // but permit Vec * Quat as an            // undocumented convenience alias            return rhs.multiply(this);        } else {            return new Vector3(this.x * rhs.x, this.y * rhs.y, this.z * rhs.z);        }    }     /**     * @param {Vector3|number} rhs     * @returns {Vector3} `this` / `rhs`     */    divide(rhs) {        if (typeof(rhs) === "number") {            return new Vector3(this.x / rhs, this.y / rhs, this.z / rhs);        } else {            return new Vector3(this.x / rhs.x, this.y / rhs.y, this.z / rhs.z);        }    }     /**     * Only `true` if `this` and `rhs`     * are *exactly* equal. See {@link Vector3.withinEpsilon}     * @param {Vector3} rhs     * @returns {boolean} `this` === `rhs`     */    equal(rhs) {        return this.x === rhs.x && this.y === rhs.y && this.z === rhs.z;    }     /**     * @param {Vector3} rhs     * @param {number} [epsilon=1e-5]     * @returns {Vector3} `this` ≈ `rhs`     */    withinEpsilon(rhs, epsilon = 1e-5) {        return (            withinEpsilon(this.x, rhs.x, epsilon) &&            withinEpsilon(this.y, rhs.y, epsilon) &&            withinEpsilon(this.z, rhs.z, epsilon)        );    }     /**     * @returns {Vector3} The euclidean length of `this`     */    length() {        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);    }     /**     * @returns {Vector3} `this` normalized to a length of 1,     * or `Vector3(0, 0, 0)` if the total length is zero.     */    normalized() {        // prevent infinities or NaNs if we try to normalize (0, 0, 0)        const epsilon = 1e-5;        const length = this.length();         if (length >= epsilon) {            return new Vector3(this.x / length, this.y / length, this.z / length);        } else {            return new Vector3(0, 0, 0);        }    }     /**     * @param {Vector3} rhs     * @returns {number} Dot product of `this` and `rhs`,     * approaching 1 if they point in a similar direction,     * -1 if opposite, and 0 if a right angle to eachother.     */    dot(rhs) {        return this.x * rhs.x + this.y * rhs.x + this.z * rhs.z;    }     /**     * @param {Vector3} rhs     * @returns {Vector3} Cross product of `this` and `rhs`     */    cross(rhs) {        const { x: ax, y: ay, z: az } = this;        const { x: bx, y: by, z: bz } = rhs;         return new Vector3(            (ay * bz) - (az * by),            (az * bx) - (ax * bz),            (ax * by) - (ay * bx),        );    }     /**     * @param {number} digits     * @returns {Vector3}     */    quantize(digits) {        return new Vector3(            quantize(this.x, digits),            quantize(this.y, digits),            quantize(this.z, digits)        );    }     /**     * @returns {Vector3}     */    round() {        return new Vector3(Math.round(this.x), Math.round(this.y), Math.round(this.z));    }     /**     * @returns {Vector3}     */    abs() {        return new Vector3(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z));    }     /**     * @param {Vector3} rhs     * @returns {Vector3}     */    distance(rhs) {        const { x: lx, y: ly, z: lz } = this;        const { x: rx, y: ry, z: rz } = rhs;        const x = (lx - rx) * (lx - rx);        const y = (ly - ry) * (ly - ry);        const z = (lz - rz) * (lz - rz);         return Math.sqrt(x + y + z);    }     /**     * @param {Vector3} origin     * @returns {Vector3} `this` - `origin`     */    relativeTo(origin) {        return this.subtract(origin);    }     /**     * @param {Vector3} rhs     * @param {number} t     * @returns {Vector3}     */    lerpTo(rhs, t) {        let { x: lx, y: ly, z: lz } = this;        let { x: rx, y: ry, z: rz } = rhs;         let x = lerp(lx, rx, t);        let y = lerp(ly, ry, t);        let z = lerp(lz, rz, t);         return new Vector3(x, y, z);    }} /** * A rotation quaternion. Quaternions represent rotations unambiguously, * unlike Euler angles which can suffer from gimbal lock, * but aren't generally human-readable. * * See {@link Quaternion.toEulerDegrees} and {@link Quaternion.fromPitchYawRollDegrees}. * * Compatible with the existing {@link Quat} type. * * {@link https://en.wikipedia.org/wiki/Quaternion} */class Quaternion {    /**     * The identity quaternion. Represents no rotation.     *     * @returns {Quaternion} `Quaternion(0, 0, 0, 1)`     */    static get IDENTITY() { return new Quaternion(0, 0, 0, 1); }     /** @type {number} */ x;    /** @type {number} */ y;    /** @type {number} */ z;    /** @type {number} */ w;     /**     * Alias of `x`     * @returns {number}     */    get [0]() { return this.x; }    /**     * Alias of `y`     * @returns {number}     */    get [1]() { return this.y; }    /**     * Alias of `z`     * @returns {number}     */    get [2]() { return this.z; }    /**     * Alias of `w`     * @returns {number}     */    get [3]() { return this.w; }     *[Symbol.iterator]() { yield this.x; yield this.y; yield this.z; yield this.w; }     /** @returns {string} */    toString() { return `Quaternion(${this.x}, ${this.y}, ${this.z}, ${this.w})`; }     /**     * @overload     * @throws Throws an error if x, y, z, or w is missing     * @param {number} x     * @param {number} y     * @param {number} z     * @param {number} w     */    /**     * @overload     * @throws Throws an error if x, y, z, or w is missing     * @param {number[]} xyzw     */    /**     * @overload     * @throws Throws an error if x, y, z, or w is missing     * @param {Object|Quaternion} quat     * @param {number} quat.x     * @param {number} quat.y     * @param {number} quat.z     * @param {number} quat.w     */    constructor(...args) {        if (args.length === 0) {            this.x = 0;            this.y = 0;            this.z = 0;            this.w = 1;        } else if (args[0] instanceof Quaternion || typeof(args[0]) === "object") {            const { x, y, z, w } = args[0];             if (                x === undefined ||                y === undefined ||                z === undefined ||                w === undefined            ) {                throw new Error("x, y, z, and w must all be specified");            }             this.x = x;            this.y = y;            this.z = z;            this.w = w;        } else if (args[0] instanceof Array) {            const [ x, y, z, w ] = args[0];             if (                x === undefined ||                y === undefined ||                z === undefined ||                w === undefined            ) {                throw new Error("x, y, z, and w must all be specified");            }             this.x = x;            this.y = y;            this.z = z;            this.w = w;        } else {            const [ x, y, z, w ] = args;             if (

Lines 1–500 of 1450.