text: // Copyright (c) 2011, Chris Umbel, James Coglan
  var Vector = require('./vector');
  var Matrix = require('./matrix');
  var Plane = require('./plane');
  var Sylvester = require('./sylvester');
  
  // Line class - depends on Vector, and some methods require Matrix and Plane.
  
  function Line() {}
  Line.prototype = {
  
    // Returns true if the argument occupies the same space as the line
    eql: function(line) {
      return (this.isParallelTo(line) && this.contains(line.anchor));
    },
  
    // Returns a copy of the line
    dup: function() {
      return Line.create(this.anchor, this.direction);
    },
  
    // Returns the result of translating the line by the given vector/array
    translate: function(vector) {
      var V = vector.elements || vector;
      return Line.create([
        this.anchor.elements[0] + V[0],
        this.anchor.elements[1] + V[1],
        this.anchor.elements[2] + (V[2] || 0)
      ], this.direction);
    },
  
    // Returns true if the line is parallel to the argument. Here, 'parallel to'
    // means that the argument's direction is either parallel or antiparallel to
    // the line's own direction. A line is parallel to a plane if the two do not
    // have a unique intersection.
    isParallelTo: function(obj) {
      if (obj.normal || (obj.start && obj.end)) { return obj.isParallelTo(this); }
      var theta = this.direction.angleFrom(obj.direction);
      return (Math.abs(theta) <= Sylvester.precision || Math.abs(theta - Math.PI) <= Sylvester.precision);
    },
  
    // Returns the line's perpendicular distance from the argument,
    // which can be a point, a line or a plane
    distanceFrom: function(obj) {
      if (obj.normal || (obj.start && obj.end)) { return obj.distanceFrom(this); }
      if (obj.direction) {
        // obj is a line
        if (this.isParallelTo(obj)) { return this.distanceFrom(obj.anchor); }
        var N = this.direction.cross(obj.direction).toUnitVector().elements;
        var A = this.anchor.elements, B = obj.anchor.elements;
        return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]);
      } else {
        // obj is a point
        var P = obj.elements || obj;
        var A = this.anchor.elements, D = this.direction.elements;
        var PA1 = P[0] - A[0], PA2 = P[1] - A[1], PA3 = (P[2] || 0) - A[2];
        var modPA = Math.sqrt(PA1*PA1 + PA2*PA2 + PA3*PA3);
        if (modPA === 0) return 0;
        // Assumes direction vector is normalized
        var cosTheta = (PA1 * D[0] + PA2 * D[1] + PA3 * D[2]) / modPA;
        var sin2 = 1 - cosTheta*cosTheta;
        return Math.abs(modPA * Math.sqrt(sin2 < 0 ? 0 : sin2));
      }
    },
  
    // Returns true iff the argument is a point on the line, or if the argument
    // is a line segment lying within the receiver
    contains: function(obj) {
      if (obj.start && obj.end) { return this.contains(obj.start) && this.contains(obj.end); }
      var dist = this.distanceFrom(obj);
      return (dist !== null && dist <= Sylvester.precision);
    },
  
    // Returns the distance from the anchor of the given point. Negative values are
    // returned for points that are in the opposite direction to the line's direction from
    // the line's anchor point.
    positionOf: function(point) {
      if (!this.contains(point)) { return null; }
      var P = point.elements || point;
      var A = this.anchor.elements, D = this.direction.elements;
      return (P[0] - A[0]) * D[0] + (P[1] - A[1]) * D[1] + ((P[2] || 0) - A[2]) * D[2];
    },
  
    // Returns true iff the line lies in the given plane
    liesIn: function(plane) {
      return plane.contains(this);
    },
  
    // Returns true iff the line has a unique point of intersection with the argument
    intersects: function(obj) {
      if (obj.normal) { return obj.intersects(this); }
      return (!this.isParallelTo(obj) && this.distanceFrom(obj) <= Sylvester.precision);
    },
  
    // Returns the unique intersection point with the argument, if one exists
    intersectionWith: function(obj) {
      if (obj.normal || (obj.start && obj.end)) { return obj.intersectionWith(this); }
      if (!this.intersects(obj)) { return null; }
      var P = this.anchor.elements, X = this.direction.elements,
          Q = obj.anchor.elements, Y = obj.direction.elements;
      var X1 = X[0], X2 = X[1], X3 = X[2], Y1 = Y[0], Y2 = Y[1], Y3 = Y[2];
      var PsubQ1 = P[0] - Q[0], PsubQ2 = P[1] - Q[1], PsubQ3 = P[2] - Q[2];
      var XdotQsubP = - X1*PsubQ1 - X2*PsubQ2 - X3*PsubQ3;
      var YdotPsubQ = Y1*PsubQ1 + Y2*PsubQ2 + Y3*PsubQ3;
      var XdotX = X1*X1 + X2*X2 + X3*X3;
      var YdotY = Y1*Y1 + Y2*Y2 + Y3*Y3;
      var XdotY = X1*Y1 + X2*Y2 + X3*Y3;
      var k = (XdotQsubP * YdotY / XdotX + XdotY * YdotPsubQ) / (YdotY - XdotY * XdotY);
      return Vector.create([P[0] + k*X1, P[1] + k*X2, P[2] + k*X3]);
    },
  
    // Returns the point on the line that is closest to the given point or line/line segment
    pointClosestTo: function(obj) {
      if (obj.start && obj.end) {
        // obj is a line segment
        var P = obj.pointClosestTo(this);
        return (P === null) ? null : this.pointClosestTo(P);
      } else if (obj.direction) {
        // obj is a line
        if (this.intersects(obj)) { return this.intersectionWith(obj); }
        if (this.isParallelTo(obj)) { return null; }
        var D = this.direction.elements, E = obj.direction.elements;
        var D1 = D[0], D2 = D[1], D3 = D[2], E1 = E[0], E2 = E[1], E3 = E[2];
        // Create plane containing obj and the shared normal and intersect this with it
        // Thank you: http://www.cgafaq.info/wiki/Line-line_distance
        var x = (D3 * E1 - D1 * E3), y = (D1 * E2 - D2 * E1), z = (D2 * E3 - D3 * E2);
        var N = [x * E3 - y * E2, y * E1 - z * E3, z * E2 - x * E1];
        var P = Plane.create(obj.anchor, N);
        return P.intersectionWith(this);
      } else {
        // obj is a point
        var P = obj.elements || obj;
        if (this.contains(P)) { return Vector.create(P); }
        var A = this.anchor.elements, D = this.direction.elements;
        var D1 = D[0], D2 = D[1], D3 = D[2], A1 = A[0], A2 = A[1], A3 = A[2];
        var x = D1 * (P[1]-A2) - D2 * (P[0]-A1), y = D2 * ((P[2] || 0) - A3) - D3 * (P[1]-A2),
            z = D3 * (P[0]-A1) - D1 * ((P[2] || 0) - A3);
        var V = Vector.create([D2 * x - D3 * z, D3 * y - D1 * x, D1 * z - D2 * y]);
        var k = this.distanceFrom(P) / V.modulus();
        return Vector.create([
          P[0] + V.elements[0] * k,
          P[1] + V.elements[1] * k,
          (P[2] || 0) + V.elements[2] * k
        ]);
      }
    },
  
    // Returns a copy of the line rotated by t radians about the given line. Works by
    // finding the argument's closest point to this line's anchor point (call this C) and
    // rotating the anchor about C. Also rotates the line's direction about the argument's.
    // Be careful with this - the rotation axis' direction affects the outcome!
    rotate: function(t, line) {
      // If we're working in 2D
      if (typeof(line.direction) == 'undefined') { line = Line.create(line.to3D(), Vector.k); }
      var R = Matrix.Rotation(t, line.direction).elements;
      var C = line.pointClosestTo(this.anchor).elements;
      var A = this.anchor.elements, D = this.direction.elements;
      var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2];
      var x = A1 - C1, y = A2 - C2, z = A3 - C3;
      return Line.create([
        C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z,
        C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z,
        C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z
      ], [
        R[0][0] * D[0] + R[0][1] * D[1] + R[0][2] * D[2],
        R[1][0] * D[0] + R[1][1] * D[1] + R[1][2] * D[2],
        R[2][0] * D[0] + R[2][1] * D[1] + R[2][2] * D[2]
      ]);
    },
  
    // Returns a copy of the line with its direction vector reversed.
    // Useful when using lines for rotations.
    reverse: function() {
      return Line.create(this.anchor, this.direction.x(-1));
    },
  
    // Returns the line's reflection in the given point or line
    reflectionIn: function(obj) {
      if (obj.normal) {
        // obj is a plane
        var A = this.anchor.elements, D = this.direction.elements;
        var A1 = A[0], A2 = A[1], A3 = A[2], D1 = D[0], D2 = D[1], D3 = D[2];
        var newA = this.anchor.reflectionIn(obj).elements;
        // Add the line's direction vector to its anchor, then mirror that in the plane
        var AD1 = A1 + D1, AD2 = A2 + D2, AD3 = A3 + D3;
        var Q = obj.pointClosestTo([AD1, AD2, AD3]).elements;
        var newD = [Q[0] + (Q[0] - AD1) - newA[0], Q[1] + (Q[1] - AD2) - newA[1], Q[2] + (Q[2] - AD3) - newA[2]];
        return Line.create(newA, newD);
      } else if (obj.direction) {
        // obj is a line - reflection obtained by rotating PI radians about obj
        return this.rotate(Math.PI, obj);
      } else {
        // obj is a point - just reflect the line's anchor in it
        var P = obj.elements || obj;
        return Line.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.direction);
      }
    },
  
    // Set the line's anchor point and direction.
    setVectors: function(anchor, direction) {
      // Need to do this so that line's properties are not
      // references to the arguments passed in
      anchor = Vector.create(anchor);
      direction = Vector.create(direction);
      if (anchor.elements.length == 2) {anchor.elements.push(0); }
      if (direction.elements.length == 2) { direction.elements.push(0); }
      if (anchor.elements.length > 3 || direction.elements.length > 3) { return null; }
      var mod = direction.modulus();
      if (mod === 0) { return null; }
      this.anchor = anchor;
      this.direction = Vector.create([
        direction.elements[0] / mod,
        direction.elements[1] / mod,
        direction.elements[2] / mod
      ]);
      return this;
    }
  };
  
  // Constructor function
  Line.create = function(anchor, direction) {
    var L = new Line();
    return L.setVectors(anchor, direction);
  };
  
  // Axes
  Line.X = Line.create(Vector.Zero(3), Vector.i);
  Line.Y = Line.create(Vector.Zero(3), Vector.j);
  Line.Z = Line.create(Vector.Zero(3), Vector.k);
  
  module.exports = Line;
  