math_vector.js

import {DEFAULT_LINEAR_TOLERANCE, DEFAULT_ANGULAR_TOLERANCE} from "../constants";
import oc from "../opencascade/initializer";

/**
 * Represents a 3D vector.
 * @memberof math
 * @alias Vector
 */
export class Vector {
  #wrapped;

  /**
   * Creates a new `Vector` instance.
   * @param {Object} [parameters] - The parameters for the vector.
   * @param {number} [parameters.x=0] - The X component of the vector.
   * @param {number} [parameters.y=0] - The Y component of the vector.
   * @param {number} [parameters.z=0] - The Z component of the vector.
   */
  constructor({x = 0, y = 0, z = 0, wrapped = undefined} = {}) {
    if (wrapped) {
      this.#wrapped = wrapped;
    } else {
      this.#wrapped = new oc.gp_Vec_4(x, y, z);
    }
  }

  /**
   * Returns the wrapped OpenCascade object.
   * @private
   */
  get wrapped() {
    return this.#wrapped;
  }

  /**
   * Gets the X component of this vector.
   */
  get x() {
    return this.#wrapped.X();
  }

  /**
   * Gets the Y component of this vector.
   */
  get y() {
    return this.#wrapped.Y();
  }

  /**
   * Gets the Z component of this vector.
   */
  get z() {
    return this.#wrapped.Z();
  }

  /**
   * Sets the X component of this vector.
   */
  set x(value) {
    this.#wrapped.SetX(value);
  }

  /**
   * Sets the Y component of this vector.
   */
  set y(value) {
    this.#wrapped.SetY(value);
  }

  /**
   * Sets the Z component of this vector.
   */
  set z(value) {
    this.#wrapped.SetZ(value);
  }

  /**
   * Gets the length (magnitude) of this vector.
   * @returns {number} The length of the vector.
   */
  get length() {
    return this.#wrapped.Magnitude();
  }

  /**
   * Adds another vector to this vector.
   * @param {Vector} other - The vector to add.
   * @returns {Vector} A new `Vector` representing the result.
   */
  add(other) {
    return new Vector({wrapped: this.#wrapped.Added(other.wrapped)});
  }

  /**
   * Subtracts another vector from this vector.
   * @param {Vector} other - The vector to subtract.
   * @returns {Vector} A new `Vector` representing the result.
   */
  subtract(other) {
    return new Vector({wrapped: this.#wrapped.Subtracted(other.wrapped)});
  }

  /**
   * Multiplies this vector by a scalar.
   * @param {number} scalar - The scalar to multiply by.
   * @returns {Vector} A new `Vector` representing the result.
   */
  multiply(scalar) {
    return new Vector({wrapped: this.#wrapped.Multiplied(scalar)});
  }

  /**
   * Divides this vector by a scalar.
   * @param {number} scalar - The scalar to divide by.
   * @returns {Vector} A new `Vector` representing the result.
   */
  divide(scalar) {
    return new Vector({wrapped: this.#wrapped.Divided(scalar)});
  }

  /**
   * Computes the cross product of this vector with another vector.
   * @param {Vector} other - The other vector.
   * @returns {Vector} A new `Vector` representing the result.
   */
  cross(other) {
    return new Vector({wrapped: this.#wrapped.Crossed(other.wrapped)});
  }

  /**
   * Computes the dot product of this vector with another vector.
   * @param {Vector} other - The other vector.
   * @returns {number} The dot product of the two vectors.
   */
  dot(other) {
    return this.#wrapped.Dot(other.wrapped);
  }

  /**
   * Rotates this vector around a specified axis by a given angle.
   * @param {Object} parameters - Rotation parameters.
   * @param {Axis} parameters.axis - The axis to rotate around.
   * @param {number} parameters.angle - The rotation angle in radians.
   * @returns {Vector} A new `Vector` representing the result.
   */
  rotate({axis, angle}) {
    // For vectors, the rotation is applied around the direction of the axis, and the origin does not influence the rotation.
    // This is because vectors represent directions without a fixed position in space.
    // That's why we use a point to perform the rotation.
    const result = new oc.gp_Pnt_3(this.#wrapped.X(), this.#wrapped.Y(), this.#wrapped.Z()).Rotated(axis.wrapped, angle);
    return new Vector({
      x: result.X(),
      y: result.Y(),
      z: result.Z(),
    });
  }

  /**
   * Scales this vector by a scalar.
   * @param {number} scalar - The scaling factor.
   * @returns {Vector} A new `Vector` representing the result.
   */
  scale(scalar) {
    return new Vector({wrapped: this.#wrapped.Scaled(scalar)});
  }

  /**
   * Mirrors this vector across a specified plane.
   * @param {Plane} plane - The plane to mirror across.
   * @returns {Vector} A new `Vector` representing the mirrored result.
   */
  mirror(plane) {
    return new Vector({wrapped: this.#wrapped.Mirrored_3(plane.wrapped.Position().Ax2())});
  }

  /**
   * Computes the distance between this vector and another vector.
   * @param {Vector} other - The other vector.
   * @returns {number} The distance between the vectors.
   */
  distance(other) {
    return this.#wrapped.Subtracted(other.wrapped).Magnitude();
  }

  /**
   * Computes the angle between this vector and another vector.
   * @param {Vector} other - The other vector.
   * @returns {number} The angle between the vectors in radians.
   */
  angle(other) {
    return this.#wrapped.Angle(other.wrapped);
  }

  /**
   * Normalizes this vector to have a length of 1.
   * @returns {Vector} A new `Vector` representing the result.
   */
  normalize() {
    return new Vector({wrapped: this.#wrapped.Normalized()});
  }

  /**
   * Checks if this vector is equal to another vector.
   * @param {Vector} other - The other vector.
   * @returns {boolean} `true` if the vectors are equal, `false` otherwise.
   */
  isEqual(other) {
    return this.#wrapped.IsEqual(other.wrapped, DEFAULT_LINEAR_TOLERANCE, DEFAULT_ANGULAR_TOLERANCE);
  }

  /**
   * Checks if this vector is perpendicular (normal) to another vector.
   * @param {Vector} other - The other vector.
   * @returns {boolean} `true` if the vectors are perpendicular, `false` otherwise.
   */
  isPerpendicular(other) {
    return this.#wrapped.IsNormal(other.wrapped, DEFAULT_ANGULAR_TOLERANCE);
  }

  /**
   * Checks if this vector is parallel to another vector.
   * @param {Vector} other - The other vector.
   * @returns {boolean} `true` if the vectors are parallel, `false` otherwise.
   */
  isParallel(other) {
    return this.#wrapped.IsParallel(other.wrapped, DEFAULT_ANGULAR_TOLERANCE);
  }

  /**
   * The ZERO vector is an empty vector.
   * @static
   */
  static ZERO = new Vector();

  /**
   * The X vector is a unit vector pointing in the positive x direction.
   * @static
   */
  static X = new Vector({x: 1});

  /**
   * The NEGATIVE_X vector is a unit vector pointing in the negative x direction.
   * @static
   */
  static NEGATIVE_X = new Vector({x: -1});

  /**
   * The Y vector is a unit vector pointing in the positive y direction.
   * @static
   */
  static Y = new Vector({y: 1});

  /**
   * The NEGATIVE_Y vector is a unit vector pointing in the negative y direction.
   * @static
   */
  static NEGATIVE_Y = new Vector({y: -1});

  /**
   * The Z vector is a unit vector pointing in the positive z direction.
   * @static
   */
  static Z = new Vector({z: 1});

  /**
   * The NEGATIVE_Z vector is a unit vector pointing in the negative z direction.
   * @static
   */
  static NEGATIVE_Z = new Vector({z: -1});
}