plane.js

import Vector3 from './vector3.js';

// Copied from Three.JS
/**
 * @author bhouston / http://exocortex.com
 */

const Plane = function (normal, constant) {

  this.normal = (normal !== undefined) ? normal : new Vector3(1, 0, 0);
  this.constant = (constant !== undefined) ? constant : 0;

};

Plane.prototype = {

  constructor: Plane,

  set (normal, constant) {

    this.normal.copy(normal);
    this.constant = constant;

    return this;

  },

  setComponents (x, y, z, w) {

    this.normal.set(x, y, z);
    this.constant = w;

    return this;

  },

  setFromNormalAndCoplanarPoint (normal, point) {

    this.normal.copy(normal);
    // Must be this.normal, not normal, as this.normal is normalized
    this.constant = -point.dot(this.normal);

    return this;

  },

  setFromCoplanarPoints: (function () {

    const v1 = new Vector3();
    const v2 = new Vector3();

    return function (a, b, c) {

      const normal = v1.subVectors(c, b).cross(v2.subVectors(a, b)).normalize();

      // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?

      this.setFromNormalAndCoplanarPoint(normal, a);

      return this;

    };

  })(),


  copy (plane) {

    this.normal.copy(plane.normal);
    this.constant = plane.constant;

    return this;

  },

  normalize () {

    // Note: will lead to a divide by zero if the plane is invalid.

    const inverseNormalLength = 1.0 / this.normal.length();

    this.normal.multiplyScalar(inverseNormalLength);
    this.constant *= inverseNormalLength;

    return this;

  },

  negate () {

    this.constant *= -1;
    this.normal.negate();

    return this;

  },

  distanceToPoint (point) {

    return this.normal.dot(point) + this.constant;

  },

  distanceToSphere (sphere) {

    return this.distanceToPoint(sphere.center) - sphere.radius;

  },

  projectPoint (point, optionalTarget) {

    return this.orthoPoint(point, optionalTarget).sub(point).negate();

  },

  orthoPoint (point, optionalTarget) {

    const perpendicularMagnitude = this.distanceToPoint(point);

    const result = optionalTarget || new Vector3();


    return result.copy(this.normal).multiplyScalar(perpendicularMagnitude);

  },

  isIntersectionLine (line) {

    // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.

    const startSign = this.distanceToPoint(line.start);
    const endSign = this.distanceToPoint(line.end);

    return (startSign < 0 && endSign > 0) || (endSign < 0 && startSign > 0);

  },

  intersectLine: (function () {

    const v1 = new Vector3();

    return function (line, optionalTarget) {

      const result = optionalTarget || new Vector3();

      const direction = line.delta(v1);

      const denominator = this.normal.dot(direction);

      if (denominator === 0) {

        // Line is coplanar, return origin
        if (this.distanceToPoint(line.start) === 0) {

          return result.copy(line.start);

        }

        // Unsure if this is the correct method to handle this case.
        return undefined;

      }

      const t = -(line.start.dot(this.normal) + this.constant) / denominator;

      if (t < 0 || t > 1) {

        return undefined;

      }

      return result.copy(direction).multiplyScalar(t).add(line.start);

    };

  })(),

  intersectPlane (targetPlane) {
    // Returns the intersection line between two planes
    const direction = this.normal.clone().cross(targetPlane.normal);
    const origin = new Vector3();
    const intersectionData = {
      origin,
      direction
    };

    // If the planes are parallel, return an empty vector for the intersection line
    if (this.normal.clone().cross(targetPlane.normal).length < 1e-10) {
      intersectionData.direction = new Vector3();

      return intersectionData;
    }

    const h1 = this.constant;
    const h2 = targetPlane.constant;
    const n1dotn2 = this.normal.clone().dot(targetPlane.normal);

    const c1 = -(h1 - h2 * n1dotn2) / (1 - n1dotn2 * n1dotn2);
    const c2 = -(h2 - h1 * n1dotn2) / (1 - n1dotn2 * n1dotn2);

    intersectionData.origin = this.normal.clone().multiplyScalar(c1).add(targetPlane.normal.clone().multiplyScalar(c2));

    return intersectionData;
  },

  coplanarPoint (optionalTarget) {

    const result = optionalTarget || new Vector3();


    return result.copy(this.normal).multiplyScalar(-this.constant);

  },

  translate (offset) {

    this.constant = this.constant - offset.dot(this.normal);

    return this;

  },

  equals (plane) {

    return plane.normal.equals(this.normal) && (plane.constant === this.constant);

  },

  clone () {

    return new Plane().copy(this);

  }
};

export default Plane;