text: import Vector3 from './Vector3';
  import vec3 from '../glmatrix/vec3';
  
  var EPSILON = 1e-5;
  
  /**
   * @constructor
   * @alias clay.Ray
   * @param {clay.Vector3} [origin]
   * @param {clay.Vector3} [direction]
   */
  var Ray = function (origin, direction) {
      /**
       * @type {clay.Vector3}
       */
      this.origin = origin || new Vector3();
      /**
       * @type {clay.Vector3}
       */
      this.direction = direction || new Vector3();
  };
  
  Ray.prototype = {
  
      constructor: Ray,
  
      // http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm
      /**
       * Calculate intersection point between ray and a give plane
       * @param  {clay.Plane} plane
       * @param  {clay.Vector3} [out]
       * @return {clay.Vector3}
       */
      intersectPlane: function (plane, out) {
          var pn = plane.normal.array;
          var d = plane.distance;
          var ro = this.origin.array;
          var rd = this.direction.array;
  
          var divider = vec3.dot(pn, rd);
          // ray is parallel to the plane
          if (divider === 0) {
              return null;
          }
          if (!out) {
              out = new Vector3();
          }
          var t = (vec3.dot(pn, ro) - d) / divider;
          vec3.scaleAndAdd(out.array, ro, rd, -t);
          out._dirty = true;
          return out;
      },
  
      /**
       * Mirror the ray against plane
       * @param  {clay.Plane} plane
       */
      mirrorAgainstPlane: function (plane) {
          // Distance to plane
          var d = vec3.dot(plane.normal.array, this.direction.array);
          vec3.scaleAndAdd(this.direction.array, this.direction.array, plane.normal.array, -d * 2);
          this.direction._dirty = true;
      },
  
      distanceToPoint: (function () {
          var v = vec3.create();
          return function (point) {
              vec3.sub(v, point, this.origin.array);
              // Distance from projection point to origin
              var b = vec3.dot(v, this.direction.array);
              if (b < 0) {
                  return vec3.distance(this.origin.array, point);
              }
              // Squared distance from center to origin
              var c2 = vec3.lenSquared(v);
              // Squared distance from center to projection point
              return Math.sqrt(c2 - b * b);
          };
      })(),
  
      /**
       * Calculate intersection point between ray and sphere
       * @param  {clay.Vector3} center
       * @param  {number} radius
       * @param  {clay.Vector3} out
       * @return {clay.Vector3}
       */
      intersectSphere: (function () {
          var v = vec3.create();
          return function (center, radius, out) {
              var origin = this.origin.array;
              var direction = this.direction.array;
              center = center.array;
              vec3.sub(v, center, origin);
              // Distance from projection point to origin
              var b = vec3.dot(v, direction);
              // Squared distance from center to origin
              var c2 = vec3.squaredLength(v);
              // Squared distance from center to projection point
              var d2 = c2 - b * b;
  
              var r2 = radius * radius;
              // No intersection
              if (d2 > r2) {
                  return;
              }
  
              var a = Math.sqrt(r2 - d2);
              // First intersect point
              var t0 = b - a;
              // Second intersect point
              var t1 = b + a;
  
              if (!out) {
                  out = new Vector3();
              }
              if (t0 < 0) {
                  if (t1 < 0) {
                      return null;
                  }
                  else {
                      vec3.scaleAndAdd(out.array, origin, direction, t1);
                      return out;
                  }
              }
              else {
                  vec3.scaleAndAdd(out.array, origin, direction, t0);
                  return out;
              }
          };
      })(),
  
      // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/
      /**
       * Calculate intersection point between ray and bounding box
       * @param {clay.BoundingBox} bbox
       * @param {clay.Vector3}
       * @return {clay.Vector3}
       */
      intersectBoundingBox: function (bbox, out) {
          var dir = this.direction.array;
          var origin = this.origin.array;
          var min = bbox.min.array;
          var max = bbox.max.array;
  
          var invdirx = 1 / dir[0];
          var invdiry = 1 / dir[1];
          var invdirz = 1 / dir[2];
  
          var tmin, tmax, tymin, tymax, tzmin, tzmax;
          if (invdirx >= 0) {
              tmin = (min[0] - origin[0]) * invdirx;
              tmax = (max[0] - origin[0]) * invdirx;
          }
          else {
              tmax = (min[0] - origin[0]) * invdirx;
              tmin = (max[0] - origin[0]) * invdirx;
          }
          if (invdiry >= 0) {
              tymin = (min[1] - origin[1]) * invdiry;
              tymax = (max[1] - origin[1]) * invdiry;
          }
          else {
              tymax = (min[1] - origin[1]) * invdiry;
              tymin = (max[1] - origin[1]) * invdiry;
          }
  
          if ((tmin > tymax) || (tymin > tmax)) {
              return null;
          }
  
          if (tymin > tmin || tmin !== tmin) {
              tmin = tymin;
          }
          if (tymax < tmax || tmax !== tmax) {
              tmax = tymax;
          }
  
          if (invdirz >= 0) {
              tzmin = (min[2] - origin[2]) * invdirz;
              tzmax = (max[2] - origin[2]) * invdirz;
          }
          else {
              tzmax = (min[2] - origin[2]) * invdirz;
              tzmin = (max[2] - origin[2]) * invdirz;
          }
  
          if ((tmin > tzmax) || (tzmin > tmax)) {
              return null;
          }
  
          if (tzmin > tmin || tmin !== tmin) {
              tmin = tzmin;
          }
          if (tzmax < tmax || tmax !== tmax) {
              tmax = tzmax;
          }
          if (tmax < 0) {
              return null;
          }
  
          var t = tmin >= 0 ? tmin : tmax;
  
          if (!out) {
              out = new Vector3();
          }
          vec3.scaleAndAdd(out.array, origin, dir, t);
          return out;
      },
  
      // http://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
      /**
       * Calculate intersection point between ray and three triangle vertices
       * @param {clay.Vector3} a
       * @param {clay.Vector3} b
       * @param {clay.Vector3} c
       * @param {boolean}           singleSided, CW triangle will be ignored
       * @param {clay.Vector3} [out]
       * @param {clay.Vector3} [barycenteric] barycentric coords
       * @return {clay.Vector3}
       */
      intersectTriangle: (function () {
  
          var eBA = vec3.create();
          var eCA = vec3.create();
          var AO = vec3.create();
          var vCross = vec3.create();
  
          return function (a, b, c, singleSided, out, barycenteric) {
              var dir = this.direction.array;
              var origin = this.origin.array;
              a = a.array;
              b = b.array;
              c = c.array;
  
              vec3.sub(eBA, b, a);
              vec3.sub(eCA, c, a);
  
              vec3.cross(vCross, eCA, dir);
  
              var det = vec3.dot(eBA, vCross);
  
              if (singleSided) {
                  if (det > -EPSILON) {
                      return null;
                  }
              }
              else {
                  if (det > -EPSILON && det < EPSILON) {
                      return null;
                  }
              }
  
              vec3.sub(AO, origin, a);
              var u = vec3.dot(vCross, AO) / det;
              if (u < 0 || u > 1) {
                  return null;
              }
  
              vec3.cross(vCross, eBA, AO);
              var v = vec3.dot(dir, vCross) / det;
  
              if (v < 0 || v > 1 || (u + v > 1)) {
                  return null;
              }
  
              vec3.cross(vCross, eBA, eCA);
              var t = -vec3.dot(AO, vCross) / det;
  
              if (t < 0) {
                  return null;
              }
  
              if (!out) {
                  out = new Vector3();
              }
              if (barycenteric) {
                  Vector3.set(barycenteric, (1 - u - v), u, v);
              }
              vec3.scaleAndAdd(out.array, origin, dir, t);
  
              return out;
          };
      })(),
  
      /**
       * Apply an affine transform matrix to the ray
       * @return {clay.Matrix4} matrix
       */
      applyTransform: function (matrix) {
          Vector3.add(this.direction, this.direction, this.origin);
          Vector3.transformMat4(this.origin, this.origin, matrix);
          Vector3.transformMat4(this.direction, this.direction, matrix);
  
          Vector3.sub(this.direction, this.direction, this.origin);
          Vector3.normalize(this.direction, this.direction);
      },
  
      /**
       * Copy values from another ray
       * @param {clay.Ray} ray
       */
      copy: function (ray) {
          Vector3.copy(this.origin, ray.origin);
          Vector3.copy(this.direction, ray.direction);
      },
  
      /**
       * Clone a new ray
       * @return {clay.Ray}
       */
      clone: function () {
          var ray = new Ray();
          ray.copy(this);
          return ray;
      }
  };
  
  export default Ray;
  