Source: Matrix.js

import { Vector } from "./Vector.js";
/**
 * Class representing a Matrix.
 */
export class Matrix {
  /**
   * Creates a new Matrix.
   * @param {number} rows - Rows (must be positive integer).
   * @param {number} cols - Columns (must be positive integer).
   */
  constructor(rows, cols) {
    if (
      rows < 1 ||
      cols < 1 ||
      !Number.isInteger(rows) ||
      !Number.isInteger(cols)
    ) {
      throw new Error("Rows and columns must be positive integers.");
    }

    this.rows = rows;
    this.cols = cols;
    this.matrix = [];

    for (let i = 0; i < rows; i++) {
      this.matrix[i] = [];
      for (let j = 0; j < cols; j++) {
        this.matrix[i][j] = 0;
      }
    }
  }

  /**
   * Creates a new {@link Vector} from a Matrix.
   * @returns {Vector}
   */
  toVector() {
    const result = this.matrix.flat();
    return Vector.fromArray(result.slice(0, 3));
  }

  /**
   * Returns a new Matrix from a given {@link Vector}.
   * @param {Vector} v - The input vector.
   * @returns {Matrix} A matrix with data rows corresponding to the vector components.
   */
  static fromVector(v) {
    const result = new Matrix(v.toArray().length, 1);
    result.matrix = [[v.x], [v.y], [v.z]];

    return result;
  }

  /**
   * Checks if two Matrixes have the same number of rows and columns.
   * @param {Matrix} a
   * @param {Matrix} b
   * @returns {boolean} True if Matrixes are of the same dimensionality, otherwise false.
   */
  static hasEqualDimensions(a, b) {
    return a.rows === b.rows && a.cols === b.cols;
  }

  /**
   * Adds two Matrixes and returns the result.
   * @param {Matrix} a
   * @param {Matrix} b
   * @returns {Matrix} A Matrix with data resulting from element-wise addition.
   */
  static add(a, b) {
    if (!(a instanceof Matrix) || !(b instanceof Matrix)) {
      throw new TypeError("Inputs must be of type Matrix.");
    }

    if (!Matrix.hasEqualDimensions(a, b)) {
      throw new TypeError("Matrices must have the sime dimensions.");
    }

    const result = new Matrix(a.rows, a.cols);

    for (let i = 0; i < a.rows; i++) {
      for (let j = 0; j < a.cols; j++) {
        result.matrix[i][j] = a.matrix[i][j] + b.matrix[i][j];
      }
    }

    return result;
  }

  /**
   * Adds values to a Matrix. Input can either be a single number, or another
   * instance of Matrix if it has the same dimensionality.
   * @param {Matrix | number} n - The input to add to this matrix.
   * @returns {Matrix} The current Matrix instance for chaining.
   */
  add(n) {
    if (n instanceof Matrix) {
      if (!Matrix.hasEqualDimensions(this, n)) {
        throw new TypeError("Matrices must be of equal size.");
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.cols; j++) {
          this.matrix[i][j] += n.matrix[i][j];
        }
      }
    } else if (typeof n === "number") {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.cols; j++) {
          this.matrix[i][j] += n;
        }
      }
    } else {
      throw new TypeError("Expected input of type Matrix or Number.");
    }
    return this;
  }

  /**
   * Multiplies a Matrix by a scalar, either a single number or another Matrix.
   * @param {Matrix | number} n - The scalar (must be a number or a Matrix).
   * @returns {Matrix} This Matrix instance for chaining.
   */
  scale(n) {
    if (n instanceof Matrix) {
      if (!Matrix.hasEqualDimensions(this, n)) {
        throw new TypeError("Matrices must be of equal size.");
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.cols; j++) {
          this.matrix[i][j] *= n.matrix[i][j];
        }
      }
    } else if (typeof n === "number") {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.cols; j++) {
          this.matrix[i][j] *= n;
        }
      }
    } else {
      throw new TypeError("Expected input of type Matrix or Number.");
    }

    return this;
  }

  /**
   * Multiply two Matrixes and return the result.
   * @param {Matrix} a
   * @param {Matrix} b
   * @returns {Matrix} The result of matrix multiplication.
   */
  static multiply(a, b) {
    if (!(a instanceof Matrix) || !(b instanceof Matrix)) {
      throw new TypeError("Inputs must both be Matrixes.");
    }
    if (a.cols !== b.rows) {
      throw new TypeError("Columns of a must equal rows of b.");
    }

    const result = new Matrix(a.cols, b.cols);

    for (let i = 0; i < a.rows; i++) {
      for (let j = 0; j < b.cols; j++) {
        let sum = 0;
        for (let k = 0; k < a.cols; k++) {
          sum += a.matrix[i][k] * b.matrix[k][j];
        }
        result.matrix[i][j] = sum;
      }
    }
    return result;
  }
}