/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module CartesianGeometry
 */
import { Geometry } from "../Geometry";
import { Point2d, Vector2d } from "../geometry3d/Point2dVector2d";
import { Range1d } from "./Range";
/**
 * Ray with xy origin and direction
 * @public
 */
export class Ray2d {
    _origin;
    _direction;
    constructor(origin, direction) {
        this._origin = origin;
        this._direction = direction;
    }
    /** Copy coordinates from origin and direction. */
    set(origin, direction) {
        this._origin.setFrom(origin);
        this._direction.setFrom(direction);
    }
    /** Create a ray with all zeros. */
    static createZero(result) {
        if (result) {
            result.origin.setZero();
            result.direction.setZero();
            return result;
        }
        return new Ray2d(Point2d.createZero(), Vector2d.createZero());
    }
    /**
     * Create from `origin` and `target` points.
     * @param origin ray origin, cloned
     * @param target end of ray direction vector. The direction vector is `target - origin`.
     * @param result optional pre-allocated object to return
     */
    static createOriginAndTarget(origin, target, result) {
        if (result) {
            result._origin.setFrom(origin);
            result._direction.set(target.x - origin.x, target.y - origin.y);
            return result;
        }
        return new Ray2d(Point2d.createFrom(origin), Vector2d.createStartEnd(origin, target));
    }
    /**
     * Create by copying coordinates from `origin` and `direction`.
     * @param origin ray origin
     * @param direction ray direction
     * @param result optional pre-allocated object to return
     */
    static createOriginAndDirection(origin, direction, result) {
        if (result) {
            result.set(origin, direction);
            return result;
        }
        return new Ray2d(Point2d.createFrom(origin), Vector2d.createFrom(direction));
    }
    /** Create from captured `origin` and `direction`. */
    static createOriginAndDirectionCapture(origin, direction, result) {
        if (result) {
            result._origin = origin;
            result._direction = direction;
            return result;
        }
        return new Ray2d(origin, direction);
    }
    /** Get the reference to the ray origin. */
    get origin() { return this._origin; }
    /** Get the reference to the ray direction. */
    get direction() { return this._direction; }
    /**
     * Return a parallel ray to the left of this ray.
     * @param leftFraction distance between rays, as a fraction of the magnitude of this ray's direction vector
     */
    parallelRay(leftFraction, result) {
        if (result) {
            this._origin.addForwardLeft(0.0, leftFraction, this._direction, result._origin);
            result._direction.setFrom(this._direction);
            return result;
        }
        return new Ray2d(this._origin.addForwardLeft(0.0, leftFraction, this._direction), this._direction.clone());
    }
    /** Return a ray with cloned origin and with direction rotated 90 degrees counterclockwise */
    ccwPerpendicularRay(result) {
        if (result) {
            result._origin.setFrom(this._origin);
            this._direction.rotate90CCWXY(result._direction);
            return result;
        }
        return new Ray2d(this._origin.clone(), this._direction.rotate90CCWXY());
    }
    /** Return a ray with cloned origin and with direction rotated 90 degrees clockwise */
    cwPerpendicularRay(result) {
        if (result) {
            result._origin.setFrom(this._origin);
            this._direction.rotate90CWXY(result._direction);
            return result;
        }
        return new Ray2d(this._origin.clone(), this._direction.rotate90CWXY());
    }
    /**
     * Normalize the direction vector in place.
     * @param defaultX value to set `this.direction.x` if normalization fails. Default value 1.
     * @param defaultY value to set `this.direction.y` if normalization fails. Default value 0.
     * @returns whether normalization succeeded (i.e., direction is nonzero)
     */
    normalizeDirectionInPlace(defaultX = 1, defaultY = 0) {
        if (this._direction.normalize(this._direction))
            return true;
        this._direction.x = defaultX;
        this._direction.y = defaultY;
        return false;
    }
    /**
     * Intersect this ray with the unbounded line defined by the given points.
     * @param linePointA start of the line
     * @param linePointB end of the line
     * @returns object with named values:
     * * `hasIntersection`: whether the intersection exists.
     * * `fraction`: ray parameter of intersection, or 0.0 if `!hasIntersection`. If the instance is normalized, this is the signed distance along the ray to the intersection point.
     * * `cross`: the 2D cross product `this.direction x (linePointB - linePointA)`, useful for determining orientation of the line and ray.
     */
    intersectUnboundedLine(linePointA, linePointB) {
        const lineDirection = Vector2d.createStartEnd(linePointA, linePointB);
        const vector0 = Vector2d.createStartEnd(linePointA, this._origin);
        const h0 = vector0.crossProduct(lineDirection);
        const dHds = this._direction.crossProduct(lineDirection);
        // h = h0 + s * dh
        const ff = Geometry.conditionalDivideFraction(-h0, dHds);
        const hasIntersection = ff !== undefined;
        return { hasIntersection, fraction: hasIntersection ? ff : 0.0, cross: dHds };
    }
    /**
     * Find the intersection of the line defined by the ray with a Range2d.
     * * Return the range of parameters (on the ray) which are "inside" the range.
     * * Note that a range is always returned; if there is no intersection it is indicated by the test `result.isNull`.
     */
    intersectionWithRange2d(range, result) {
        if (range.isNull)
            return Range1d.createNull(result);
        const interval = Range1d.createXX(-Geometry.largeCoordinateResult, Geometry.largeCoordinateResult, result);
        interval.clipLinearMapToInterval(this.origin.x, this.direction.x, range.low.x, range.high.x);
        interval.clipLinearMapToInterval(this.origin.y, this.direction.y, range.low.y, range.high.y);
        return interval;
    }
    /** Return the ray fraction where the given point projects onto the ray. */
    projectionFraction(point) {
        return this._origin.vectorTo(point).fractionOfProjectionToVector(this._direction);
    }
    /** Return the ray fraction where the given point projects onto the perpendicular ray. */
    perpendicularProjectionFraction(point) {
        const uv = this._direction.crossProduct(this._origin.vectorTo(point));
        const uu = this._direction.magnitudeSquared();
        // Want zero returned if failure case, not undefined
        return Geometry.safeDivideFraction(uv, uu, 0.0);
    }
    /** Compute and return origin plus scaled direction. */
    fractionToPoint(f, result) {
        return this._origin.plusScaled(this._direction, f, result);
    }
}
//# sourceMappingURL=Ray2d.js.map