👔Vector

customclass ⟩ Vector

support vector operations:u + v, u - v, k * v ...

u + v: u.plus(v)
u - v: u.minus(v)
k * v: v.times(k)
au + bv + cw: linearCombination([a,b,c], [u,v,w])

👉 custom objects

// 2023.01.22 - 21:04 (/) refactor vec(x,y) -> vec(...args)
// 2023.01.21 - 13:18 (+) .clampInRect()
// 2023.01.15 - 19:56 (+) linearCombination()
// 2023.01.15 - 08:50 (?) refactor, first recorded.
// ------------------------------------------------------

const {sin, cos, sqrt} = Math;

// ⭐️ import 
// ------------------------------------------------------
import {  } from './Number+ext.js';    // 👔 Number + ext


// ⭐️ Vector
// ------------------------------------------------------
// - vec(x, y), vec(v)
// - polar(r, theta)
// - linearCombination(scalars, vectors)
// ------------------------------------------------------
// 🔸 .x
// 🔸 .y
// 🔸 .coords         : [x, y]
// 🔸 .inverse        : -v            (additive inverse)
// 🔹 .plus(...args)  : (v), (x, y)
// 🔹 .minus(...args) : (v), (x, y)
// 🔹 .times(k)       : u * k         (scalar product)
// ------------------------------------------------------
// 🔸 .length
// 🔹 .distanceTo(...args)    : (v), (x, y)
// ------------------------------------------------------
// 🔹 .toString()
// ------------------------------------------------------
// 
class Vector {

    // 🟧 Vector.zero
    static get zero() { return new Vector(0, 0) }

    // init
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    // 🔸 .coords
    get coords() { return [this.x, this.y] }

    // -----------
    //     -v
    // -----------

    // 🔸 .inverse
    get inverse() { return new Vector(-this.x, -this.y) }
    
    // -------------
    //     u + v
    // -------------

    // 🔹 u.plus(v) or u.plus(x, y)
    plus(...args) {

        // treat as u + v
        if (args.length === 1) {
            const v = args[0];
            return new Vector(this.x + v.x, this.y + v.y)
        }

        // treat as u + (x, y)
        if (args.length === 2) return this.plus(vec(...args));

        // throw error otherwise
        throw new Error(`Vector.plus() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // -------------
    //     u - v
    // -------------

    // 🔹 u.minus(v) or u.minus(x, y)
    minus(...args) { 
        // treat as u - v
        if (args.length === 1) {
            const v = args[0];
            return new Vector(this.x - v.x, this.y - v.y);
        }

        // treat as u - (x, y)
        if (args.length === 2) return this.minus(vec(...args));

        // throw error otherwise
        throw new Error(`Vector.minus() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // -------------
    //     k * v
    // -------------

    // 🔹 .times(k)
    times(k) { return new Vector(this.x * k, this.y * k) }

    // ------------------------
    //     distance related
    // ------------------------

    // 🔸 .length
    get length() { 
        const {x, y} = this;
        return sqrt(x*x + y*y); 
    }

    // 🔹 .distanceTo(v) or .distanceTo(x, y)
    distanceTo(...args) {

        // u.distanceTo(v)
        if (args.length === 1) {
            const v = args[0];
            return this.minus(v).length;
        }

        // u.distanceTo(x, y)
        if (args.length === 2) {
            return this.distanceTo(vec(...args));
        }

        // throw error otherwise
        throw new Error(`Vector.distanceTo() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // 🔹 .isEqualTo(v)
    isEqualTo(v, {threshold = 100 * Number.EPSILON}={}) {
        return areEqualNumbers(0, this.distanceTo(v), {threshold});
    }

    // ---------------
    //     rect
    // ---------------

    clampInRect(rect) {
        const {x: x1, y: y1} = rect.origin;
        const {x: x2, y: y2} = rect.bottomRight;
        return vec(this.x.clamp(x1, x2), this.y.clamp(y1, y2)); // 👔 Number + ext
    }

    // ---------------
    //     debug
    // ---------------

    // 🔹 .toString()
    toString() { return `(${this.x}, ${this.y})` }

    // ⭐️ custom type name
    // ❗️ note: typeof vec(1,2) === 'object' 
    get [Symbol.toStringTag]() { return 'Vector' }
}

// ----------------------------------------
// ⭐️ convenience factory functions
// ----------------------------------------

// vec(x, y), vec(v)        // 👔 Vector (v1.1)
function vec(...args) { 
    const len = args.length;
    // treat as vec(x, y)
    if (len === 2) return new Vector(...args);
    // treat as vec(v)
    if (len === 1) {
        const {x, y} = args[0];
        return new Vector(x, y)
    };
    // throw error otherwise
    throw new Error(`vec(): expecting 1 or 2 arguments, but got ${len}!`);
}

// polar(r, theta)
function polar(r, theta) { 
    return vec(r * cos(theta), r * sin(theta)) 
}

// linear combination: a1*v1 + a2*v2 + ... + an*vn
function linearCombination(scalars, vectors) {
    return vectors
        .map((v, i) => v.times(scalars[i] || 0))
        .reduce((sum, vec) => sum.plus(vec), Vector.zero);
}

// --------------
// ⭐️ helpers
// --------------

// ⭐️ areEqualNumbers(x, y {threshold})
// - Number.EPSILON = 2^(-52) ≈ 2.2 * 10^(-16)
function areEqualNumbers(x, y, {threshold = Number.EPSILON}={}) { 
    return Math.abs(x - y) < threshold; 
}

// 📤 export
// -----------------------------
export { vec, polar, Vector, linearCombination };            // export ES module
// module.exports = { vec, polar, Vector, linearCombination };

History

  • replit ⟩ vector (with canvas example)

0: (?) first recorded, refactor.
1: (+) linearCombination()
2: (+) .clampInRect()

Last updated