Source: ray/cast.js

/**
 * Cast ray to find closest intersection with tested mapped objects.
 *
 * @method Raycaster.Ray#cast
 * @memberof Raycaster.Ray
 * @instance
 * @since 0.6.0
 *
 * @param {object} [options] - options that may include:
 * @param {object[]} [options.objects = {Raycaster#mappedObjects}] - Array of game objects to test. If not provided test all mapped game objects.
 * @param {Phaser.Geom.Point|Point} [options.target] - Ray's target point. Used in other casting methods to determine if ray was targeting mapped objects point.
 * @param {boolean} [options.internal = false] - Flag determining if method is used by other casting method.
 *
 * @return {(Phaser.Geom.Point|boolean)} Ray's closest intersection with tested objects. Returns false if no intersection has been found. Additionally contains reference to hit mapped object and segment if available.
 */
export function cast(options = {}) {
    let closestIntersection;
    let closestSegment;
    let closestObject;
    let closestDistance = this.rayRange;
    let internal = options.internal ? options.internal : false;
    let startTime = performance.now();
    let stats = {
        method: 'cast',
        rays: 1,
        testedMappedObjects: 0,
        hitMappedObjects: 0,
        segments: 0,
        time: 0
    };

    //if bounding box is defined check bounding box intersection
    if(this._raycaster && this._raycaster.boundingBox) {
        let intersections = [];
        Phaser.Geom.Intersects.GetLineToRectangle(this._ray, this._raycaster.boundingBox.rectangle, intersections);
        if(intersections.length === 1)
            closestIntersection = intersections[0];
        else if(intersections.length > 1) {
            for(let intersection of intersections) {
                let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, intersection.x, intersection.y);
                if(distance < closestDistance) {
                    closestDistance = distance;
                    closestIntersection = intersection;
                }
            }
        }
        //if ray target is declared
        else if(options.target){
            let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, options.target.x, options.target.y);
            //if target is within ray range
            if(this.rayRange > distance) {
                closestDistance = distance;
                closestIntersection = options.target;
            }
        }
    }

    //if no objects to cast ray were passed, use raycasters mapped objects
    if(!options.objects) {
        if(this._raycaster)
            options.objects = this._raycaster.mappedObjects;
        else
            return intersections;
    }
    
    for(let object of options.objects) {
        let map, boundingBox, boundingBoxIntersections = [], canTestMap = false;
        
        if(object.type === 'body' || object.type === 'composite')
            map = object.raycasterMap;
        else
            map = object.data.get('raycasterMap');

        stats.testedMappedObjects++;

        //get slightly enlarged bounding box due to fridge cases, when ray "glanced" border box's corner (v0.10.1)
        if(internal) {
            boundingBox = map._boundingBox;
        }
        else {
            boundingBox = map.getBoundingBox();
            boundingBox.setTo(boundingBox.x - 0.1, boundingBox.y - 0.1, boundingBox.width + 0.2, boundingBox.height + 0.2);
        }

        //check if object is intersected by ray
        if(Phaser.Geom.Intersects.GetLineToRectangle(this._ray, boundingBox, boundingBoxIntersections).length === 0)
            continue;

        //check if bounding box is closer than closest intersection
        if(Phaser.Geom.Rectangle.ContainsPoint(boundingBox, this.origin)) {
            canTestMap = true;
        }
        else {
            for(let boundingBoxIntersection of boundingBoxIntersections) {
                if(Phaser.Math.Distance.Between(this.origin.x, this.origin.y, boundingBoxIntersection.x, boundingBoxIntersection.y) < closestDistance) {
                    canTestMap = true;
                    break;
                }
            }
        }

        if(!canTestMap)
            continue;

        stats.hitMappedObjects++;
        stats.segments += map.getSegments(this).length;
        
        //check intersections
        for(let segment of map.getSegments(this)) {
            let intersection = [];

            //if target point is segmemt point
            if(options.target) {
                if(
                    Phaser.Geom.Point.Equals(options.target, segment.getPointA())
                    || Phaser.Geom.Point.Equals(options.target, segment.getPointB())
                ) {
                    intersection = options.target;
                }
                else if(!Phaser.Geom.Intersects.LineToLine(this._ray, segment, intersection))
                    continue;
            }
            //if no intersection continue
            else if(!Phaser.Geom.Intersects.LineToLine(this._ray, segment, intersection))
              continue;
            
            //get closest intersection
            let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, intersection.x, intersection.y);
            if(distance < closestDistance) {
                closestDistance = distance;
                closestIntersection = intersection;
                closestObject = map.object;
                closestSegment = segment;
            }
        }

        //check if map is circular
        if(map.circle) {
           //if circular map has generated points (besides tangent points to ray)
            if(map._points.length > 0) {
                continue;
            }
            
            //check if target point is a circle tangent point to ray
            if(options.target) {
                let points = map.getPoints(this);
                let isTangent = false;
                for(let point of points) {
                    if(Phaser.Geom.Point.Equals(options.target, point)) {
                        //get closest intersection
                        let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, point.x, point.y);

                        if(distance < closestDistance) {
                            closestDistance = distance;
                            closestIntersection = point;
                            closestObject = map.object;
                            isTangent = true;
                            break;
                        }
                    }
                }

                if(isTangent)
                    continue;
            }

            let circleIntersections = [];
            let offset = new Phaser.Geom.Point();
            offset.x = map.object.x - map.object.displayWidth * (map.object.originX - 0.5);
            offset.y = map.object.y - map.object.displayHeight * (map.object.originY - 0.5);

            //calculate circle's center after rotation
            let rotation = map.object.rotation;
            if(rotation !== 0) {
                let vector = new Phaser.Geom.Line(map.object.x, map.object.y, offset.x, offset.y);
                Phaser.Geom.Line.SetToAngle(vector, map.object.x, map.object.y, Phaser.Geom.Line.Angle(vector) + rotation, Phaser.Geom.Line.Length(vector));
                let cB = vector.getPointB();
                offset.x = cB.x;
                offset.y = cB.y;
            }

            //create transformed circle
            let circle = new Phaser.Geom.Circle(offset.x, offset.y, map.object.radius * map.object.scaleX);

            if(Phaser.Geom.Intersects.GetLineToCircle(this._ray, circle, circleIntersections)) {
                for(let intersection of circleIntersections) {
                    //get closest intersection
                    let distance = Phaser.Math.Distance.Between(this._ray.x1, this._ray.y1, intersection.x, intersection.y);

                    if(distance < closestDistance) {

                        closestDistance = distance;
                        closestIntersection = intersection;
                        closestObject = map.object;
                    }
                }
            }
        }

        //check container map's circles
        if(map.type == 'Container' && map._circles.length > 0) {
            for(let circle of map._circles) {
                //check if target point is a circle tangent point to ray
                if(options.target) {
                    let isTangent = false;

                    for(let point of circle.points) {
                        if(Phaser.Geom.Point.Equals(options.target, point)) {
                            //get closest intersection
                            let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, point.x, point.y);

                            if(distance < closestDistance) {
                                closestDistance = distance;
                                closestIntersection = point;
                                closestObject = map.object;
                                isTangent = true;
                                break;
                            }
                        }
                    }

                    if(isTangent)
                        continue;
                }

                let circleIntersections = [];

                if(Phaser.Geom.Intersects.GetLineToCircle(this._ray, circle, circleIntersections)) {
                    for(let intersection of circleIntersections) {
                        //get closest intersection
                        let distance = Phaser.Math.Distance.Between(this._ray.x1, this._ray.y1, intersection.x, intersection.y);

                        if(distance < closestDistance) {
                            closestDistance = distance;
                            closestIntersection = intersection;
                            closestObject = map.object;
                        }
                    }
                }
            }
        }
    }

    //update stats
    if(internal) {
        this._stats.rays++;
        this._stats.testedMappedObjects += stats.testedMappedObjects;
        this._stats.hitMappedObjects += stats.hitMappedObjects;
        this._stats.segments += stats.segments;
    }
    else {
        stats.time = performance.now() - startTime;
        this._stats = stats;
    }

    let result;
    if(!closestIntersection) {
        if(this.ignoreNotIntersectedRays)
            return false;

        result = this._ray.getPointB();
    }
    else {
        result = new Phaser.Geom.Point(closestIntersection.x, closestIntersection.y);
        result.segment = closestSegment;
        result.object = closestObject;
    }

    if(this.round) {
        result.x = Math.round(result.x);
        result.y = Math.round(result.y);
    }

    if(!internal)
        this.drawDebug([result]);
    
    return result;
}