#

API Docs for: 0.6.1
Show: Inherited Protected Private Deprecated

# File: src/collision/Ray.js

    module.exports = Ray;

    var Vec3 = require('../math/Vec3');
    var Quaternion = require('../math/Quaternion');
    var Transform = require('../math/Transform');
    var ConvexPolyhedron = require('../shapes/ConvexPolyhedron');
    var Box = require('../shapes/Box');
    var RaycastResult = require('../collision/RaycastResult');
    var Shape = require('../shapes/Shape');
    var AABB = require('../collision/AABB');

    /**
     * A line in 3D space that intersects bodies and return points.
     * @class Ray
     * @constructor
     * @param {Vec3} from
     * @param {Vec3} to
     */
    function Ray(from, to){
        /**
         * @property {Vec3} from
         */
        this.from = from ? from.clone() : new Vec3();

        /**
         * @property {Vec3} to
         */
        this.to = to ? to.clone() : new Vec3();

        /**
         * @private
         * @property {Vec3} _direction
         */
        this._direction = new Vec3();

        /**
         * The precision of the ray. Used when checking parallelity etc.
         * @property {Number} precision
         */
        this.precision = 0.0001;

        /**
         * Set to true if you want the Ray to take .collisionResponse flags into account on bodies and shapes.
         * @property {Boolean} checkCollisionResponse
         */
        this.checkCollisionResponse = true;

        /**
         * If set to true, the ray skips any hits with normal.dot(rayDirection) < 0.
         * @property {Boolean} skipBackfaces
         */
        this.skipBackfaces = false;

        /**
         * @property {number} collisionFilterMask
         * @default -1
         */
        this.collisionFilterMask = -1;

        /**
         * @property {number} collisionFilterGroup
         * @default -1
         */
        this.collisionFilterGroup = -1;

        /**
         * The intersection mode. Should be Ray.ANY, Ray.ALL or Ray.CLOSEST.
         * @property {number} mode
         */
        this.mode = Ray.ANY;

        /**
         * Current result object.
         * @property {RaycastResult} result
         */
        this.result = new RaycastResult();

        /**
         * Will be set to true during intersectWorld() if the ray hit anything.
         * @property {Boolean} hasHit
         */
        this.hasHit = false;

        /**
         * Current, user-provided result callback. Will be used if mode is Ray.ALL.
         * @property {Function} callback
         */
        this.callback = function(result){};
    }
    Ray.prototype.constructor = Ray;

    Ray.CLOSEST = 1;
    Ray.ANY = 2;
    Ray.ALL = 4;

    var tmpAABB = new AABB();
    var tmpArray = [];

    /**
     * Do itersection against all bodies in the given World.
     * @method intersectWorld
     * @param  {World} world
     * @param  {object} options
     * @return {Boolean} True if the ray hit anything, otherwise false.
     */
    Ray.prototype.intersectWorld = function (world, options) {
        this.mode = options.mode || Ray.ANY;
        this.result = options.result || new RaycastResult();
        this.skipBackfaces = !!options.skipBackfaces;
        this.collisionFilterMask = typeof(options.collisionFilterMask) !== 'undefined' ? options.collisionFilterMask : -1;
        this.collisionFilterGroup = typeof(options.collisionFilterGroup) !== 'undefined' ? options.collisionFilterGroup : -1;
        if(options.from){
            this.from.copy(options.from);
        }
        if(options.to){
            this.to.copy(options.to);
        }
        this.callback = options.callback || function(){};
        this.hasHit = false;

        this.result.reset();
        this._updateDirection();

        this.getAABB(tmpAABB);
        tmpArray.length = 0;
        world.broadphase.aabbQuery(world, tmpAABB, tmpArray);
        this.intersectBodies(tmpArray);

        return this.hasHit;
    };

    var v1 = new Vec3(),
        v2 = new Vec3();

    /*
     * As per "Barycentric Technique" as named here http://www.blackpawn.com/texts/pointinpoly/default.html But without the division
     */
    Ray.pointInTriangle = pointInTriangle;
    function pointInTriangle(p, a, b, c) {
        c.vsub(a,v0);
        b.vsub(a,v1);
        p.vsub(a,v2);

        var dot00 = v0.dot( v0 );
        var dot01 = v0.dot( v1 );
        var dot02 = v0.dot( v2 );
        var dot11 = v1.dot( v1 );
        var dot12 = v1.dot( v2 );

        var u,v;

        return  ( (u = dot11 * dot02 - dot01 * dot12) >= 0 ) &&
                ( (v = dot00 * dot12 - dot01 * dot02) >= 0 ) &&
                ( u + v < ( dot00 * dot11 - dot01 * dot01 ) );
    }

    /**
     * Shoot a ray at a body, get back information about the hit.
     * @method intersectBody
     * @private
     * @param {Body} body
     * @param {RaycastResult} [result] Deprecated - set the result property of the Ray instead.
     */
    var intersectBody_xi = new Vec3();
    var intersectBody_qi = new Quaternion();
    Ray.prototype.intersectBody = function (body, result) {
        if(result){
            this.result = result;
            this._updateDirection();
        }
        var checkCollisionResponse = this.checkCollisionResponse;

        if(checkCollisionResponse && !body.collisionResponse){
            return;
        }

        if((this.collisionFilterGroup & body.collisionFilterMask)===0 || (body.collisionFilterGroup & this.collisionFilterMask)===0){
            return;
        }

        var xi = intersectBody_xi;
        var qi = intersectBody_qi;

        for (var i = 0, N = body.shapes.length; i < N; i++) {
            var shape = body.shapes[i];

            if(checkCollisionResponse && !shape.collisionResponse){
                continue; // Skip
            }

            body.quaternion.mult(body.shapeOrientations[i], qi);
            body.quaternion.vmult(body.shapeOffsets[i], xi);
            xi.vadd(body.position, xi);

            this.intersectShape(
                shape,
                qi,
                xi,
                body
            );

            if(this.result._shouldStop){
                break;
            }
        }
    };

    /**
     * @method intersectBodies
     * @param {Array} bodies An array of Body objects.
     * @param {RaycastResult} [result] Deprecated
     */
    Ray.prototype.intersectBodies = function (bodies, result) {
        if(result){
            this.result = result;
            this._updateDirection();
        }

        for ( var i = 0, l = bodies.length; !this.result._shouldStop && i < l; i ++ ) {
            this.intersectBody(bodies[i]);
        }
    };

    /**
     * Updates the _direction vector.
     * @private
     * @method _updateDirection
     */
    Ray.prototype._updateDirection = function(){
        this.to.vsub(this.from, this._direction);
        this._direction.normalize();
    };

    /**
     * @method intersectShape
     * @private
     * @param {Shape} shape
     * @param {Quaternion} quat
     * @param {Vec3} position
     * @param {Body} body
     */
    Ray.prototype.intersectShape = function(shape, quat, position, body){
        var from = this.from;

        // Checking boundingSphere
        var distance = distanceFromIntersection(from, this._direction, position);
        if ( distance > shape.boundingSphereRadius ) {
            return;
        }

        var intersectMethod = this[shape.type];
        if(intersectMethod){
            intersectMethod.call(this, shape, quat, position, body);
        }
    };

    var vector = new Vec3();
    var normal = new Vec3();
    var intersectPoint = new Vec3();

    var a = new Vec3();
    var b = new Vec3();
    var c = new Vec3();
    var d = new Vec3();

    var tmpRaycastResult = new RaycastResult();

    /**
     * @method intersectBox
     * @private
     * @param  {Shape} shape
     * @param  {Quaternion} quat
     * @param  {Vec3} position
     * @param  {Body} body
     */
    Ray.prototype.intersectBox = function(shape, quat, position, body){
        return this.intersectConvex(shape.convexPolyhedronRepresentation, quat, position, body);
    };
    Ray.prototype[Shape.types.BOX] = Ray.prototype.intersectBox;

    /**
     * @method intersectPlane
     * @private
     * @param  {Shape} shape
     * @param  {Quaternion} quat
     * @param  {Vec3} position
     * @param  {Body} body
     */
    Ray.prototype.intersectPlane = function(shape, quat, position, body){
        var from = this.from;
        var to = this.to;
        var direction = this._direction;

        // Get plane normal
        var worldNormal = new Vec3(0, 0, 1);
        quat.vmult(worldNormal, worldNormal);

        var len = new Vec3();
        from.vsub(position, len);
        var planeToFrom = len.dot(worldNormal);
        to.vsub(position, len);
        var planeToTo = len.dot(worldNormal);

        if(planeToFrom * planeToTo > 0){
            // "from" and "to" are on the same side of the plane... bail out
            return;
        }

        if(from.distanceTo(to) < planeToFrom){
            return;
        }

        var n_dot_dir = worldNormal.dot(direction);

        if (Math.abs(n_dot_dir) < this.precision) {
            // No intersection
            return;
        }

        var planePointToFrom = new Vec3();
        var dir_scaled_with_t = new Vec3();
        var hitPointWorld = new Vec3();

        from.vsub(position, planePointToFrom);
        var t = -worldNormal.dot(planePointToFrom) / n_dot_dir;
        direction.scale(t, dir_scaled_with_t);
        from.vadd(dir_scaled_with_t, hitPointWorld);

        this.reportIntersection(worldNormal, hitPointWorld, shape, body, -1);
    };
    Ray.prototype[Shape.types.PLANE] = Ray.prototype.intersectPlane;

    /**
     * Get the world AABB of the ray.
     * @method getAABB
     * @param  {AABB} aabb
     */
    Ray.prototype.getAABB = function(result){
        var to = this.to;
        var from = this.from;
        result.lowerBound.x = Math.min(to.x, from.x);
        result.lowerBound.y = Math.min(to.y, from.y);
        result.lowerBound.z = Math.min(to.z, from.z);
        result.upperBound.x = Math.max(to.x, from.x);
        result.upperBound.y = Math.max(to.y, from.y);
        result.upperBound.z = Math.max(to.z, from.z);
    };

    var intersectConvexOptions = {
        faceList: [0]
    };

    /**
     * @method intersectHeightfield
     * @private
     * @param  {Shape} shape
     * @param  {Quaternion} quat
     * @param  {Vec3} position
     * @param  {Body} body
     */
    Ray.prototype.intersectHeightfield = function(shape, quat, position, body){
        var data = shape.data,
            w = shape.elementSize,
            worldPillarOffset = new Vec3();

        // Convert the ray to local heightfield coordinates
        var localRay = new Ray(this.from, this.to);
        Transform.pointToLocalFrame(position, quat, localRay.from, localRay.from);
        Transform.pointToLocalFrame(position, quat, localRay.to, localRay.to);

        // Get the index of the data points to test against
        var index = [];
        var iMinX = null;
        var iMinY = null;
        var iMaxX = null;
        var iMaxY = null;

        var inside = shape.getIndexOfPosition(localRay.from.x, localRay.from.y, index, false);
        if(inside){
            iMinX = index[0];
            iMinY = index[1];
            iMaxX = index[0];
            iMaxY = index[1];
        }
        inside = shape.getIndexOfPosition(localRay.to.x, localRay.to.y, index, false);
        if(inside){
            if (iMinX === null || index[0] < iMinX) { iMinX = index[0]; }
            if (iMaxX === null || index[0] > iMaxX) { iMaxX = index[0]; }
            if (iMinY === null || index[1] < iMinY) { iMinY = index[1]; }
            if (iMaxY === null || index[1] > iMaxY) { iMaxY = index[1]; }
        }

        if(iMinX === null){
            return;
        }

        var minMax = [];
        shape.getRectMinMax(iMinX, iMinY, iMaxX, iMaxY, minMax);
        var min = minMax[0];
        var max = minMax[1];

        // // Bail out if the ray can't touch the bounding box
        // // TODO
        // var aabb = new AABB();
        // this.getAABB(aabb);
        // if(aabb.intersects()){
        //     return;
        // }

        for(var i = iMinX; i <= iMaxX; i++){
            for(var j = iMinY; j <= iMaxY; j++){

                if(this.result._shouldStop){
                    return;
                }

                // Lower triangle
                shape.getConvexTrianglePillar(i, j, false);
                Transform.pointToWorldFrame(position, quat, shape.pillarOffset, worldPillarOffset);
                this.intersectConvex(shape.pillarConvex, quat, worldPillarOffset, body, intersectConvexOptions);

                if(this.result._shouldStop){
                    return;
                }

                // Upper triangle
                shape.getConvexTrianglePillar(i, j, true);
                Transform.pointToWorldFrame(position, quat, shape.pillarOffset, worldPillarOffset);
                this.intersectConvex(shape.pillarConvex, quat, worldPillarOffset, body, intersectConvexOptions);
            }
        }
    };
    Ray.prototype[Shape.types.HEIGHTFIELD] = Ray.prototype.intersectHeightfield;

    var Ray_intersectSphere_intersectionPoint = new Vec3();
    var Ray_intersectSphere_normal = new Vec3();

    /**
     * @method intersectSphere
     * @private
     * @param  {Shape} shape
     * @param  {Quaternion} quat
     * @param  {Vec3} position
     * @param  {Body} body
     */
    Ray.prototype.intersectSphere = function(shape, quat, position, body){
        var from = this.from,
            to = this.to,
            r = shape.radius;

        var a = Math.pow(to.x - from.x, 2) + Math.pow(to.y - from.y, 2) + Math.pow(to.z - from.z, 2);
        var b = 2 * ((to.x - from.x) * (from.x - position.x) + (to.y - from.y) * (from.y - position.y) + (to.z - from.z) * (from.z - position.z));
        var c = Math.pow(from.x - position.x, 2) + Math.pow(from.y - position.y, 2) + Math.pow(from.z - position.z, 2) - Math.pow(r, 2);

        var delta = Math.pow(b, 2) - 4 * a * c;

        var intersectionPoint = Ray_intersectSphere_intersectionPoint;
        var normal = Ray_intersectSphere_normal;

        if(delta < 0){
            // No intersection
            return;

        } else if(delta === 0){
            // single intersection point
            from.lerp(to, delta, intersectionPoint);

            intersectionPoint.vsub(position, normal);
            normal.normalize();

            this.reportIntersection(normal, intersectionPoint, shape, body, -1);

        } else {
            var d1 = (- b - Math.sqrt(delta)) / (2 * a);
            var d2 = (- b + Math.sqrt(delta)) / (2 * a);

            if(d1 >= 0 && d1 <= 1){
                from.lerp(to, d1, intersectionPoint);
                intersectionPoint.vsub(position, normal);
                normal.normalize();
                this.reportIntersection(normal, intersectionPoint, shape, body, -1);
            }

            if(this.result._shouldStop){
                return;
            }

            if(d2 >= 0 && d2 <= 1){
                from.lerp(to, d2, intersectionPoint);
                intersectionPoint.vsub(position, normal);
                normal.normalize();
                this.reportIntersection(normal, intersectionPoint, shape, body, -1);
            }
        }
    };
    Ray.prototype[Shape.types.SPHERE] = Ray.prototype.intersectSphere;

    var intersectConvex_normal = new Vec3();
    var intersectConvex_minDistNormal = new Vec3();
    var intersectConvex_minDistIntersect = new Vec3();
    var intersectConvex_vector = new Vec3();

    /**
     * @method intersectConvex
     * @private
     * @param  {Shape} shape
     * @param  {Quaternion} quat
     * @param  {Vec3} position
     * @param  {Body} body
     * @param {object} [options]
     * @param {array} [options.faceList]
     */
    Ray.prototype.intersectConvex = function intersectConvex(
        shape,
        quat,
        position,
        body,
        options
    ){
        var minDistNormal = intersectConvex_minDistNormal;
        var normal = intersectConvex_normal;
        var vector = intersectConvex_vector;
        var minDistIntersect = intersectConvex_minDistIntersect;
        var faceList = (options && options.faceList) || null;

        // Checking faces
        var faces = shape.faces,
            vertices = shape.vertices,
            normals = shape.faceNormals;
        var direction = this._direction;

        var from = this.from;
        var to = this.to;
        var fromToDistance = from.distanceTo(to);

        var minDist = -1;
        var Nfaces = faceList ? faceList.length : faces.length;
        var result = this.result;

        for (var j = 0; !result._shouldStop && j < Nfaces; j++) {
            var fi = faceList ? faceList[j] : j;

            var face = faces[fi];
            var faceNormal = normals[fi];
            var q = quat;
            var x = position;

            // determine if ray intersects the plane of the face
            // note: this works regardless of the direction of the face normal

            // Get plane point in world coordinates...
            vector.copy(vertices[face[0]]);
            q.vmult(vector,vector);
            vector.vadd(x,vector);

            // ...but make it relative to the ray from. We'll fix this later.
            vector.vsub(from,vector);

            // Get plane normal
            q.vmult(faceNormal,normal);

            // If this dot product is negative, we have something interesting
            var dot = direction.dot(normal);

            // Bail out if ray and plane are parallel
            if ( Math.abs( dot ) < this.precision ){
                continue;
            }

            // calc distance to plane
            var scalar = normal.dot(vector) / dot;

            // if negative distance, then plane is behind ray
            if (scalar < 0){
                continue;
            }

            // if (dot < 0) {

            // Intersection point is from + direction * scalar
            direction.mult(scalar,intersectPoint);
            intersectPoint.vadd(from,intersectPoint);

            // a is the point we compare points b and c with.
            a.copy(vertices[face[0]]);
            q.vmult(a,a);
            x.vadd(a,a);

            for(var i = 1; !result._shouldStop && i < face.length - 1; i++){
                // Transform 3 vertices to world coords
                b.copy(vertices[face[i]]);
                c.copy(vertices[face[i+1]]);
                q.vmult(b,b);
                q.vmult(c,c);
                x.vadd(b,b);
                x.vadd(c,c);

                var distance = intersectPoint.distanceTo(from);

                if(!(pointInTriangle(intersectPoint, a, b, c) || pointInTriangle(intersectPoint, b, a, c)) || distance > fromToDistance){
                    continue;
                }

                this.reportIntersection(normal, intersectPoint, shape, body, fi);
            }
            // }
        }
    };
    Ray.prototype[Shape.types.CONVEXPOLYHEDRON] = Ray.prototype.intersectConvex;

    var intersectTrimesh_normal = new Vec3();
    var intersectTrimesh_localDirection = new Vec3();
    var intersectTrimesh_localFrom = new Vec3();
    var intersectTrimesh_localTo = new Vec3();
    var intersectTrimesh_worldNormal = new Vec3();
    var intersectTrimesh_worldIntersectPoint = new Vec3();
    var intersectTrimesh_localAABB = new AABB();
    var intersectTrimesh_triangles = [];
    var intersectTrimesh_treeTransform = new Transform();

    /**
     * @method intersectTrimesh
     * @private
     * @param  {Shape} shape
     * @param  {Quaternion} quat
     * @param  {Vec3} position
     * @param  {Body} body
     * @param {object} [options]
     * @todo Optimize by transforming the world to local space first.
     * @todo Use Octree lookup
     */
    Ray.prototype.intersectTrimesh = function intersectTrimesh(
        mesh,
        quat,
        position,
        body,
        options
    ){
        var normal = intersectTrimesh_normal;
        var triangles = intersectTrimesh_triangles;
        var treeTransform = intersectTrimesh_treeTransform;
        var minDistNormal = intersectConvex_minDistNormal;
        var vector = intersectConvex_vector;
        var minDistIntersect = intersectConvex_minDistIntersect;
        var localAABB = intersectTrimesh_localAABB;
        var localDirection = intersectTrimesh_localDirection;
        var localFrom = intersectTrimesh_localFrom;
        var localTo = intersectTrimesh_localTo;
        var worldIntersectPoint = intersectTrimesh_worldIntersectPoint;
        var worldNormal = intersectTrimesh_worldNormal;
        var faceList = (options && options.faceList) || null;

        // Checking faces
        var indices = mesh.indices,
            vertices = mesh.vertices,
            normals = mesh.faceNormals;

        var from = this.from;
        var to = this.to;
        var direction = this._direction;

        var minDist = -1;
        treeTransform.position.copy(position);
        treeTransform.quaternion.copy(quat);

        // Transform ray to local space!
        Transform.vectorToLocalFrame(position, quat, direction, localDirection);
        //body.vectorToLocalFrame(direction, localDirection);
        Transform.pointToLocalFrame(position, quat, from, localFrom);
        //body.pointToLocalFrame(from, localFrom);
        Transform.pointToLocalFrame(position, quat, to, localTo);
        //body.pointToLocalFrame(to, localTo);
        var fromToDistanceSquared = localFrom.distanceSquared(localTo);

        mesh.tree.rayQuery(this, treeTransform, triangles);

        for (var i = 0, N = triangles.length; !this.result._shouldStop && i !== N; i++) {
            var trianglesIndex = triangles[i];

            mesh.getNormal(trianglesIndex, normal);

            // determine if ray intersects the plane of the face
            // note: this works regardless of the direction of the face normal

            // Get plane point in world coordinates...
            mesh.getVertex(indices[trianglesIndex * 3], a);

            // ...but make it relative to the ray from. We'll fix this later.
            a.vsub(localFrom,vector);

            // Get plane normal
            // quat.vmult(normal, normal);

            // If this dot product is negative, we have something interesting
            var dot = localDirection.dot(normal);

            // Bail out if ray and plane are parallel
            // if (Math.abs( dot ) < this.precision){
            //     continue;
            // }

            // calc distance to plane
            var scalar = normal.dot(vector) / dot;

            // if negative distance, then plane is behind ray
            if (scalar < 0){
                continue;
            }

            // Intersection point is from + direction * scalar
            localDirection.scale(scalar,intersectPoint);
            intersectPoint.vadd(localFrom,intersectPoint);

            // Get triangle vertices
            mesh.getVertex(indices[trianglesIndex * 3 + 1], b);
            mesh.getVertex(indices[trianglesIndex * 3 + 2], c);

            var squaredDistance = intersectPoint.distanceSquared(localFrom);

            if(!(pointInTriangle(intersectPoint, b, a, c) || pointInTriangle(intersectPoint, a, b, c)) || squaredDistance > fromToDistanceSquared){
                continue;
            }

            // transform intersectpoint and normal to world
            Transform.vectorToWorldFrame(quat, normal, worldNormal);
            //body.vectorToWorldFrame(normal, worldNormal);
            Transform.pointToWorldFrame(position, quat, intersectPoint, worldIntersectPoint);
            //body.pointToWorldFrame(intersectPoint, worldIntersectPoint);
            this.reportIntersection(worldNormal, worldIntersectPoint, mesh, body, trianglesIndex);
        }
        triangles.length = 0;
    };
    Ray.prototype[Shape.types.TRIMESH] = Ray.prototype.intersectTrimesh;

    /**
     * @method reportIntersection
     * @private
     * @param  {Vec3} normal
     * @param  {Vec3} hitPointWorld
     * @param  {Shape} shape
     * @param  {Body} body
     * @return {boolean} True if the intersections should continue
     */
    Ray.prototype.reportIntersection = function(normal, hitPointWorld, shape, body, hitFaceIndex){
        var from = this.from;
        var to = this.to;
        var distance = from.distanceTo(hitPointWorld);
        var result = this.result;

        // Skip back faces?
        if(this.skipBackfaces && normal.dot(this._direction) > 0){
            return;
        }

        result.hitFaceIndex = typeof(hitFaceIndex) !== 'undefined' ? hitFaceIndex : -1;

        switch(this.mode){
        case Ray.ALL:
            this.hasHit = true;
            result.set(
                from,
                to,
                normal,
                hitPointWorld,
                shape,
                body,
                distance
            );
            result.hasHit = true;
            this.callback(result);
            break;

        case Ray.CLOSEST:

            // Store if closer than current closest
            if(distance < result.distance || !result.hasHit){
                this.hasHit = true;
                result.hasHit = true;
                result.set(
                    from,
                    to,
                    normal,
                    hitPointWorld,
                    shape,
                    body,
                    distance
                );
            }
            break;

        case Ray.ANY:

            // Report and stop.
            this.hasHit = true;
            result.hasHit = true;
            result.set(
                from,
                to,
                normal,
                hitPointWorld,
                shape,
                body,
                distance
            );
            result._shouldStop = true;
            break;
        }
    };

    var v0 = new Vec3(),
        intersect = new Vec3();
    function distanceFromIntersection(from, direction, position) {

        // v0 is vector from from to position
        position.vsub(from,v0);
        var dot = v0.dot(direction);

        // intersect = direction*dot + from
        direction.mult(dot,intersect);
        intersect.vadd(from,intersect);

        var distance = position.distanceTo(intersect);

        return distance;
    }