# classes/ray.js

    "use strict";

    module.exports = function(Flatten) {
        let {Point, Segment, Line, Circle, Arc, Box, Vector} = Flatten;
        /**
         * Class representing a horizontal ray, used by ray shooting algorithm
         * @type {Ray}
         */
        Flatten.Ray = class Ray {
            /**
             * Construct ray by setting start point
             * @param {Point} pt - start point
             */
            constructor(...args) {
                this.pt = new Point();

                if (args.length == 0) {
                    return;
                }

                if (args.length == 1 && args[0] instanceof Point) {
                    this.pt = args[0].clone();
                    return;
                }

                if (args.length == 2 && typeof(args[0]) == "number" && typeof(args[1]) == "number") {
                    this.pt = new Point(args[0], args[1]);
                    return;
                }

                throw Flatten.Errors.ILLEGAL_PARAMETERS;
            }

            /**
             * Returns copied instance of the ray object
             * @returns {Ray}
             */
            clone() {
                return new Ray(this.pt);
            }

            /**
             * Returns half-infinite bounding box of the ray
             * @returns {Box} - bounding box
             */
            get box() {
                return new Box(
                    this.pt.x,
                    this.pt.y,
                    Number.POSITIVE_INFINITY,
                    this.pt.y
                )
            }

            /**
             * Return ray start point
             * @returns {Point} - ray start point
             */
            get start() {
                return this.pt;
            }

            /**
             * Return ray normal vector (0,1) - horizontal ray
             * @returns {Vector} - ray normal vector
             */
            get norm() {
                return new Vector(0,1);
            }

            /**
             * Returns array of intersection points between ray and segment or arc
             * @param {Segment|Arc} - Shape to intersect with ray
             * @returns {Array} array of intersection points
             */
            intersect(shape) {
                if (shape instanceof Segment) {
                    return this.intersectRay2Segment(this, shape);
                }

                if (shape instanceof Arc) {
                    return this.intersectRay2Arc(this, shape);
                }
            }

            intersectRay2Segment(ray, segment) {
                let ip = [];

                if (ray.box.not_intersect(segment.box)) {
                    return ip;
                }

                let line = new Line(ray.start, ray.norm);
                let ip_tmp = line.intersect(segment);

                for (let pt of ip_tmp) {
                    if (Flatten.Utils.GE(pt.x, ray.start.x)) {
                        ip.push(pt);
                    }
                }

                /* If there were two intersection points between line and ray,
                and now there is exactly one left, it means ray starts between these points
                and there is another intersection point - start of the ray */
                if (ip_tmp.length == 2 && ip.length == 1 && ray.start.on(line)) {
                    ip.push(ray.start);
                }

                return ip;
            }

            intersectRay2Arc(ray, arc) {
                let ip = [];

                if (ray.box.not_intersect(arc.box)) {
                    return ip;
                }

                let line = new Line(ray.start, ray.norm);
                let ip_tmp = line.intersect(arc);

                for (let pt of ip_tmp) {
                    if (Flatten.Utils.GE(pt.x, ray.start.x)) {
                        ip.push(pt);
                    }
                }
                return ip;
            }
        };

        Flatten.ray = (...args) => new Flatten.Ray(...args);
    };