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;