modeling_edge.js

import oc from "../opencascade/initializer";
import explore from "../utils/explore";
import cachedGetters from "../utils/cache";
import {Vertex} from "./index";
import {Vector} from "../math";
import {DEFAULT_ANGULAR_TOLERANCE} from "../constants";

/**
 * Represents an edge in 3D space.
 * @memberof modeling
 * @alias Edge
 */
export class Edge {
  #wrapped;
  #curve;

  /**
   * @hideconstructor
   */
  constructor(wrapped) {
    this.#wrapped = oc.TopoDS.Edge_1(wrapped);
  }

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

  /**
   * Retrieves all vertices of the edge.
   * @returns {Vertex[]} An array of `Vertex` objects representing the vertices of the edge.
   */
  get vertices() {
    return explore({shape: this.#wrapped, find: oc.TopAbs_ShapeEnum.TopAbs_VERTEX}).map((shape) => new Vertex(shape));
  }

  /**
   * Calculates the length of the edge.
   * @returns {number} The length of the edge.
   */
  get length() {
    return oc.GCPnts_AbscissaPoint.Length_1(this.#asCurve());
  }

  /**
   * Determines the type of the edge's curve.
   * @returns {string} The type of the edge (e.g., "Line", "Circle").
   */
  get type() {
    switch (this.#asCurve().GetType()) {
      case oc.GeomAbs_CurveType.GeomAbs_Line:
        return Edge.TYPE.Line;
      case oc.GeomAbs_CurveType.GeomAbs_Circle:
        return Edge.TYPE.Circle;
      case oc.GeomAbs_CurveType.GeomAbs_Ellipse:
        return Edge.TYPE.Ellipse;
      case oc.GeomAbs_CurveType.GeomAbs_Hyperbola:
        return Edge.TYPE.Hyperbola;
      case oc.GeomAbs_CurveType.GeomAbs_Parabola:
        return Edge.TYPE.Parabola;
      case oc.GeomAbs_CurveType.GeomAbs_BezierCurve:
        return Edge.TYPE.BezierCurve;
      case oc.GeomAbs_CurveType.GeomAbs_BSplineCurve:
        return Edge.TYPE.BSplineCurve;
      case oc.GeomAbs_CurveType.GeomAbs_OffsetCurve:
        return Edge.TYPE.OffsetCurve;
      case oc.GeomAbs_CurveType.GeomAbs_OtherCurve:
        return Edge.TYPE.OtherCurve;
    }
  }

  /**
   * Retrieves the point at a specified normalized distance along the edge.
   * @param {number} value - The normalized distance along the edge (between 0 and 1).
   * @returns {Vector} A `Vector` object representing the point at the given distance.
   */
  pointAt(value) {
    const param = this.#parameterAt(value);
    const point = this.#asCurve().Value(param);
    return new Vector({
      x: point.X(),
      y: point.Y(),
      z: point.Z(),
    });
  }

  /**
   * Retrieves the point at a specified distance along the edge.
   * @param {number} value - The distance along the edge (between 0 and edge.length()).
   * @returns {Vector} A `Vector` object representing the point at the given distance.
   */
  pointAtLength(value) {
    const param = this.#parameterAtLength(value);
    const point = this.#asCurve().Value(param);
    return new Vector({
      x: point.X(),
      y: point.Y(),
      z: point.Z(),
    });
  }

  /**
   * Retrieves the tangent vector at a specified normalized distance along the edge.
   * @param {number} value - The normalized distance along the edge (between 0 and 1).
   * @returns {Vector} A `Vector` object representing the tangent vector at the given distance.
   */
  tangentAt(value) {
    const param = this.#parameterAt(value);
    const point = new oc.gp_Pnt_1();
    const vector = new oc.gp_Vec_1();
    this.#asCurve().D1(param, point, vector);
    const tangent = new oc.gp_Dir_2(vector);
    return new Vector({
      x: tangent.X(),
      y: tangent.Y(),
      z: tangent.Z(),
    }).normalize();
  }

  /**
   * Checks if the edge is parallel to a given direction vector. Only applicable for line edges.
   * @param {Vector} direction - The direction vector.
   * @returns {boolean|undefined} `true` if the edge is parallel to the direction, `false` otherwise, or `undefined` if not a line.
   */
  isParallel(direction) {
    if (this.type() !== Edge.TYPE.Line) {
      return undefined;
    }
    const dir = new oc.gp_Dir_4(direction.x, direction.y, direction.z);
    return this.#asCurve().Line().Direction().IsParallel(dir, DEFAULT_ANGULAR_TOLERANCE);
  }

  /**
   * Checks if the edge is perpendicular to a given direction vector. Only applicable for line edges.
   * @param {Vector} direction - The direction vector.
   * @returns {boolean|undefined} `true` if the edge is perpendicular to the direction, `false` otherwise, or `undefined` if not a line.
   */
  isPerpendicular(direction) {
    if (this.type() !== Edge.TYPE.Line) {
      return undefined;
    }
    const dir = new oc.gp_Dir_4(direction.x, direction.y, direction.z);
    return this.#asCurve().Line().Direction().IsNormal(dir, DEFAULT_ANGULAR_TOLERANCE);
  }

  /**
   * Computes the hash code for the solid.
   * @returns {number} The hash code.
   * @private
   */
  hashCode() {
    return oc.OCJS.HashCode(this.#wrapped);
  }

  #parameterAt(value) {
    const curve = this.#asCurve();
    return new oc.GCPnts_AbscissaPoint_2(curve, value * this.length, curve.FirstParameter()).Parameter();
  }
  
  #parameterAtLength(value) {
    const curve = this.#asCurve();
    return new oc.GCPnts_AbscissaPoint_2(curve, value, curve.FirstParameter()).Parameter();
  }

  #asCurve() {
    if (!this.#curve) {
      this.#curve = new oc.BRepAdaptor_Curve_2(this.#wrapped);
    }
    return this.#curve;
  }

  static TYPE = Object.freeze({
    Line: "Line",
    Circle: "Circle",
    Ellipse: "Ellipse",
    Hyperbola: "Hyperbola",
    Parabola: "Parabola",
    BezierCurve: "BezierCurve",
    BSplineCurve: "BSplineCurve",
    OffsetCurve: "OffsetCurve",
    OtherCurve: "OtherCurve",
  });
}

cachedGetters({object: Edge, properties: ["vertices", "length", "type"]});