Source: math/Ray.js

import { Vector3 } from './Vector3.js';

var _vec3_1 = new Vector3();

var _diff = new Vector3();
var _edge1 = new Vector3();
var _edge2 = new Vector3();
var _normal = new Vector3();

/**
 * @constructor
 * @memberof zen3d
 * @param {zen3d.Vector3} [origin=]
 * @param {zen3d.Vector3} [direction=]
 */
function Ray(origin, direction) {
	this.origin = (origin !== undefined) ? origin : new Vector3();
	this.direction = (direction !== undefined) ? direction : new Vector3();
}

Object.assign(Ray.prototype, /** @lends zen3d.Ray.prototype */{

	/**
     *
     */
	set: function(origin, direction) {
		this.origin.copy(origin);
		this.direction.copy(direction);
	},

	/**
     *
     */
	at: function(t, optionalTarget) {
		var result = optionalTarget || new Vector3();

		return result.copy(this.direction).multiplyScalar(t).add(this.origin);
	},

	/**
     * @method
     */
	intersectsSphere: function(sphere, optionalTarget) {
		_vec3_1.subVectors(sphere.center, this.origin);
		var tca = _vec3_1.dot(this.direction);
		var d2 = _vec3_1.dot(_vec3_1) - tca * tca;
		var radius2 = sphere.radius * sphere.radius;
		if (d2 > radius2) {
			return null;
		}

		var thc = Math.sqrt(radius2 - d2);

		// t0 = first intersect point - entrance on front of sphere
		var t0 = tca - thc;

		// t1 = second intersect point - exit point on back of sphere
		var t1 = tca + thc;
		// console.log(t0, t1);
		// test to see if both t0 and t1 are behind the ray - if so, return null
		if (t0 < 0 && t1 < 0) {
			return null;
		}
		// test to see if t0 is behind the ray:
		// if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
		// in order to always return an intersect point that is in front of the ray.
		if (t0 < 0) {
			return this.at(t1, optionalTarget);
		}

		// else t0 is in front of the ray, so return the first collision point scaled by t0
		return this.at(t0, optionalTarget);
	},

	/**
     *
     */
	intersectsBox: function(box, optionalTarget) {
		var tmin, tmax, tymin, tymax, tzmin, tzmax;

		var invdirx = 1 / this.direction.x,
			invdiry = 1 / this.direction.y,
			invdirz = 1 / this.direction.z;

		var origin = this.origin;

		if (invdirx >= 0) {
			tmin = (box.min.x - origin.x) * invdirx;
			tmax = (box.max.x - origin.x) * invdirx;
		} else {
			tmin = (box.max.x - origin.x) * invdirx;
			tmax = (box.min.x - origin.x) * invdirx;
		}

		if (invdiry >= 0) {
			tymin = (box.min.y - origin.y) * invdiry;
			tymax = (box.max.y - origin.y) * invdiry;
		} else {
			tymin = (box.max.y - origin.y) * invdiry;
			tymax = (box.min.y - origin.y) * invdiry;
		}

		if ((tmin > tymax) || (tymin > tmax)) return null;

		// These lines also handle the case where tmin or tmax is NaN
		// (result of 0 * Infinity). x !== x returns true if x is NaN

		if (tymin > tmin || tmin !== tmin) tmin = tymin;

		if (tymax < tmax || tmax !== tmax) tmax = tymax;

		if (invdirz >= 0) {
			tzmin = (box.min.z - origin.z) * invdirz;
			tzmax = (box.max.z - origin.z) * invdirz;
		} else {
			tzmin = (box.max.z - origin.z) * invdirz;
			tzmax = (box.min.z - origin.z) * invdirz;
		}

		if ((tmin > tzmax) || (tzmin > tmax)) return null;

		if (tzmin > tmin || tmin !== tmin) tmin = tzmin;

		if (tzmax < tmax || tmax !== tmax) tmax = tzmax;

		// return point closest to the ray (positive side)

		if (tmax < 0) return null;

		return this.at(tmin >= 0 ? tmin : tmax, optionalTarget);
	},

	/**
     * @method
     */
	intersectTriangle: function(a, b, c, backfaceCulling, optionalTarget) {
		// Compute the offset origin, edges, and normal.

		// from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h

		_edge1.subVectors(b, a);
		_edge2.subVectors(c, a);
		_normal.crossVectors(_edge1, _edge2);

		// Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
		// E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
		//   |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
		//   |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
		//   |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
		var DdN = this.direction.dot(_normal);
		var sign;
		if (DdN > 0) {
			if (backfaceCulling) return null;
			sign = 1;
		} else if (DdN < 0) {
			sign = -1;
			DdN = -DdN;
		} else {
			return null;
		}

		_diff.subVectors(this.origin, a);
		var DdQxE2 = sign * this.direction.dot(_edge2.crossVectors(_diff, _edge2));

		// b1 < 0, no intersection
		if (DdQxE2 < 0) {
			return null;
		}

		var DdE1xQ = sign * this.direction.dot(_edge1.cross(_diff));

		// b2 < 0, no intersection
		if (DdE1xQ < 0) {
			return null;
		}

		// b1+b2 > 1, no intersection
		if (DdQxE2 + DdE1xQ > DdN) {
			return null;
		}

		// Line intersects triangle, check if ray does.
		var QdN = -sign * _diff.dot(_normal);

		// t < 0, no intersection
		if (QdN < 0) {
			return null;
		}

		// Ray intersects triangle.
		return this.at(QdN / DdN, optionalTarget);
	},

	/**
     *
     */
	copy: function(ray) {
		this.origin.copy(ray.origin);
		this.direction.copy(ray.direction);

		return this;
	},

	/**
     *
     */
	applyMatrix4: function(matrix4) {
		this.direction.add(this.origin).applyMatrix4(matrix4);
		this.origin.applyMatrix4(matrix4);
		this.direction.sub(this.origin);
		this.direction.normalize();

		return this;
	}

});

export { Ray };