/*---------------------------------------------------------------------------------------------
* 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 { Angle } from "./Angle";
/**
 * Minimal object containing x,y and operations that are meaningful without change in both point and vector.
 *  * `XY` is not instantiable.
 *  * The derived (instantiable) classes are
 *    * `Point2d`
 *    * `Vector2d`
 * @public
 */
export class XY {
    /** x component */
    x;
    /** y component */
    y;
    /** Set both x and y. */
    set(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
    /** Set both x and y to zero */
    setZero() {
        this.x = 0;
        this.y = 0;
    }
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
    /** Set both x and y from other. */
    setFrom(other) {
        if (other) {
            this.x = other.x;
            this.y = other.y;
        }
        else {
            this.x = 0;
            this.y = 0;
        }
    }
    /** Freeze this instance so it is read-only */
    freeze() {
        return Object.freeze(this);
    }
    /** Returns true if this and other have equal x,y parts within Geometry.smallMetricDistance. */
    isAlmostEqual(other, tol) {
        return Geometry.isSameCoordinate(this.x, other.x, tol) && Geometry.isSameCoordinate(this.y, other.y, tol);
    }
    /** Returns true if this and other have equal x,y parts within Geometry.smallMetricDistance. */
    isAlmostEqualXY(x, y, tol) {
        return Geometry.isSameCoordinate(this.x, x, tol) && Geometry.isSameCoordinate(this.y, y, tol);
    }
    /** Return a json array  `[x,y]`   */
    toJSON() {
        return [this.x, this.y];
    }
    /** Return a json object `{x: 1, y:2}`  */
    toJSONXY() {
        return { x: this.x, y: this.y };
    }
    /**
     * Set x and y from a JSON input such as `[1,2]` or `{x:1, y:2}`
     * * If no JSON input is provided, 0 would be used as default values for x and y.
     * @param json the JSON input
     */
    setFromJSON(json) {
        if (Array.isArray(json)) {
            this.set(json[0] || 0, json[1] || 0);
            return;
        }
        if (json) {
            this.set(json.x || 0, json.y || 0);
            return;
        }
        this.set(0, 0);
    }
    /** Return the distance from this point to other */
    distance(other) {
        const xDist = other.x - this.x;
        const yDist = other.y - this.y;
        return Math.sqrt(xDist * xDist + yDist * yDist);
    }
    /** Return squared distance from this point to other */
    distanceSquared(other) {
        const xDist = other.x - this.x;
        const yDist = other.y - this.y;
        return xDist * xDist + yDist * yDist;
    }
    /** Return the largest absolute distance between corresponding components */
    maxDiff(other) {
        return Math.max(Math.abs(this.x - other.x), Math.abs(this.y - other.y));
    }
    /** Return the x,y component corresponding to 0,1 */
    at(index) {
        if (index < 0.5)
            return this.x;
        return this.y;
    }
    /** Set value at index 0 or 1 */
    setAt(index, value) {
        if (index < 0.5)
            this.x = value;
        else
            this.y = value;
    }
    /** Return the index (0,1) of the x,y component with largest absolute value */
    indexOfMaxAbs() {
        let index = 0;
        const a = Math.abs(this.x);
        const b = Math.abs(this.y);
        if (b > a) {
            index = 1;
        }
        return index;
    }
    /** Returns true if the x,y components are both small by metric metric tolerance */
    get isAlmostZero() {
        return Geometry.isSmallMetricDistance(this.x) && Geometry.isSmallMetricDistance(this.y);
    }
    /** Return true if the x and y components are all exactly zero */
    get isZero() {
        return this.x === 0.0 && this.y === 0.0;
    }
    /** Return the largest absolute value of any component */
    maxAbs() {
        return Math.max(Math.abs(this.x), Math.abs(this.y));
    }
    /** Return the magnitude of the vector */
    magnitude() {
        return Math.sqrt(this.x * this.x + this.y * this.y);
    }
    /** Return the squared magnitude of the vector.  */
    magnitudeSquared() {
        return this.x * this.x + this.y * this.y;
    }
    /** Returns true if the x,y components are exactly equal. */
    isExactEqual(other) {
        return this.x === other.x && this.y === other.y;
    }
    /** Returns true if x,y match `other` within metric tolerance */
    isAlmostEqualMetric(other, distanceTol = Geometry.smallMetricDistance) {
        return this.maxDiff(other) <= distanceTol;
    }
    /** Return a (full length) vector from this point to other */
    vectorTo(other, result) {
        return Vector2d.create(other.x - this.x, other.y - this.y, result);
    }
    /** Return a unit vector from this point to other */
    unitVectorTo(other, result) {
        return this.vectorTo(other, result).normalize(result);
    }
    /** Cross product of vectors from origin to targets */
    static crossProductToPoints(origin, targetA, targetB) {
        return Geometry.crossProductXYXY(targetA.x - origin.x, targetA.y - origin.y, targetB.x - origin.x, targetB.y - origin.y);
    }
    /** Multiply the x, y parts by scale, and return the instance. */
    scaleInPlace(scale) {
        this.x *= scale;
        this.y *= scale;
        return this;
    }
}
/** 2D point with `x`,`y` as properties
 * @public
 */
export class Point2d extends XY {
    /** Constructor for Point2d */
    constructor(x = 0, y = 0) {
        super(x, y);
    }
    /** Return a new Point2d with x,y coordinates from this. */
    clone(result) {
        return Point2d.create(this.x, this.y, result);
    }
    /**
     * Return a point (newly created unless result provided) with given x,y coordinates
     * @param x x coordinate
     * @param y y coordinate
     * @param result optional result
     */
    static create(x = 0, y = 0, result) {
        if (result) {
            result.x = x;
            result.y = y;
            return result;
        }
        return new Point2d(x, y);
    }
    /**
     * Set x and y from a JSON input such as `[1,2]` or `{x:1, y:2}`
     * * If no JSON input is provided, 0 would be used as default values for x and y.
     * @param json the JSON input
     */
    static fromJSON(json) {
        const val = new Point2d();
        val.setFromJSON(json);
        return val;
    }
    /** Create (or optionally reuse) a Point2d from another object with fields x and y */
    static createFrom(xy, result) {
        if (xy)
            return Point2d.create(xy.x, xy.y, result);
        return Point2d.create(0, 0, result);
    }
    /** Create a Point2d with both coordinates zero. */
    static createZero(result) {
        return Point2d.create(0, 0, result);
    }
    /**
     * Starting at this point, move along `vector` by `tangentFraction` of its length, and then
     * by `leftFraction` of its length along the left perpendicular.
     * @param tangentFraction distance to move along `vector`, as a fraction of its length
     * @param leftFraction distance to move perpendicular to `vector`, as a fraction of its length
     * @param vector the other vector
     */
    addForwardLeft(tangentFraction, leftFraction, vector, result) {
        const dx = vector.x;
        const dy = vector.y;
        return Point2d.create(this.x + tangentFraction * dx - leftFraction * dy, this.y + tangentFraction * dy + leftFraction * dx, result);
    }
    /**
     * Interpolate at tangentFraction between this instance and point, and then Move by leftFraction
     * along the xy perpendicular of the vector between the points.
     */
    forwardLeftInterpolate(tangentFraction, leftFraction, point) {
        const dx = point.x - this.x;
        const dy = point.y - this.y;
        return Point2d.create(this.x + tangentFraction * dx - leftFraction * dy, this.y + tangentFraction * dy + leftFraction * dx);
    }
    /** Return a point interpolated between this point and the right param. */
    interpolate(fraction, other, result) {
        if (fraction <= 0.5)
            return Point2d.create(this.x + fraction * (other.x - this.x), this.y + fraction * (other.y - this.y), result);
        const t = fraction - 1.0;
        return Point2d.create(other.x + t * (other.x - this.x), other.y + t * (other.y - this.y), result);
    }
    /** Return a point with independent x,y fractional interpolation. */
    interpolateXY(fractionX, fractionY, other, result) {
        return Point2d.create(Geometry.interpolate(this.x, fractionX, other.x), Geometry.interpolate(this.y, fractionY, other.y), result);
    }
    /** Return this point minus vector */
    minus(vector, result) {
        return Point2d.create(this.x - vector.x, this.y - vector.y, result);
    }
    /** Return point plus vector */
    plus(vector, result) {
        return Point2d.create(this.x + vector.x, this.y + vector.y, result);
    }
    /** Return point plus vector */
    plusXY(dx = 0, dy = 0, result) {
        return Point2d.create(this.x + dx, this.y + dy, result);
    }
    /** Return point + vector * scalar */
    plusScaled(vector, scaleFactor, result) {
        return Point2d.create(this.x + vector.x * scaleFactor, this.y + vector.y * scaleFactor, result);
    }
    /** Return point + vectorA * scalarA + vectorB * scalarB */
    plus2Scaled(vectorA, scalarA, vectorB, scalarB, result) {
        return Point2d.create(this.x + vectorA.x * scalarA + vectorB.x * scalarB, this.y + vectorA.y * scalarA + vectorB.y * scalarB, result);
    }
    /** Return point + vectorA * scalarA + vectorB * scalarB + vectorC * scalarC */
    plus3Scaled(vectorA, scalarA, vectorB, scalarB, vectorC, scalarC, result) {
        return Point2d.create(this.x + vectorA.x * scalarA + vectorB.x * scalarB + vectorC.x * scalarC, this.y + vectorA.y * scalarA + vectorB.y * scalarB + vectorC.y * scalarC, result);
    }
    /** Point2d equivalent of [[Vector2d.createAdd2Scaled]]. */
    static createAdd2Scaled(pointA, scaleA, pointB, scaleB, result) {
        return Point2d.create(pointA.x * scaleA + pointB.x * scaleB, pointA.y * scaleA + pointB.y * scaleB, result);
    }
    /** Point2d equivalent of [[Vector2d.createAdd2ScaledXY]]. */
    static createAdd2ScaledXY(ax, ay, scaleA, bx, by, scaleB, result) {
        return Point2d.create(ax * scaleA + bx * scaleB, ay * scaleA + by * scaleB, result);
    }
    /** Point2d equivalent of [[Vector2d.createAdd3Scaled]]. */
    static createAdd3Scaled(pointA, scaleA, pointB, scaleB, pointC, scaleC, result) {
        return Point2d.create(pointA.x * scaleA + pointB.x * scaleB + pointC.x * scaleC, pointA.y * scaleA + pointB.y * scaleB + pointC.y * scaleC, result);
    }
    /** Point2d equivalent of [[Vector2d.createAdd3ScaledXY]]. */
    static createAdd3ScaledXY(ax, ay, scaleA, bx, by, scaleB, cx, cy, scaleC, result) {
        return Point2d.create(ax * scaleA + bx * scaleB + cx * scaleC, ay * scaleA + by * scaleB + cy * scaleC, result);
    }
    /**
     * Return the dot product of vector from this to targetA and vector from this to targetB
     * @param targetA target of first vector
     * @param targetB target of second vector
     */
    dotVectorsToTargets(targetA, targetB) {
        return (targetA.x - this.x) * (targetB.x - this.x) + (targetA.y - this.y) * (targetB.y - this.y);
    }
    /**
     * Returns the (scalar) cross product of vector from this to targetA and vector from this to targetB
     * @param target1 target of first vector
     * @param target2 target of second vector
     */
    crossProductToPoints(target1, target2) {
        const x1 = target1.x - this.x;
        const y1 = target1.y - this.y;
        const x2 = target2.x - this.x;
        const y2 = target2.y - this.y;
        return x1 * y2 - y1 * x2;
    }
    /**
     * Return the fractional coordinate of the projection of this instance x,y onto the
     * line from startPoint to endPoint.
     * @param startPoint start point of line
     * @param endPoint end point of line
     * @param defaultFraction fraction to return if startPoint and endPoint are equal.
     */
    fractionOfProjectionToLine(startPoint, endPoint, defaultFraction = 0) {
        const denominator = startPoint.distanceSquared(endPoint);
        if (denominator < Geometry.smallMetricDistanceSquared)
            return defaultFraction;
        const numerator = startPoint.dotVectorsToTargets(endPoint, this);
        return numerator / denominator;
    }
}
/**
 * 2D vector with `x`,`y` as properties
 * @public
 */
export class Vector2d extends XY {
    constructor(x = 0, y = 0) {
        super(x, y);
    }
    /** Return a new Vector2d with the same x,y */
    clone(result) {
        return Vector2d.create(this.x, this.y, result);
    }
    /** Return a new Vector2d with given x and y */
    static create(x = 0, y = 0, result) {
        if (result) {
            result.x = x;
            result.y = y;
            return result;
        }
        return new Vector2d(x, y);
    }
    /**
     * Return a (new) Vector2d with components scale,0
     * If scale is not given default value 1 is used.
     */
    static unitX(scale = 1) {
        return new Vector2d(scale, 0);
    }
    /**
     * Return a (new) Vector2d with components 0,scale
     * If scale is not given default value 1 is used.
     */
    static unitY(scale = 1) {
        return new Vector2d(0, scale);
    }
    /** Return a Vector2d with components 0,0 */
    static createZero(result) {
        return Vector2d.create(0, 0, result);
    }
    /** Copy contents from another Point3d, Point2d, Vector2d, or Vector3d, or leading entries of Float64Array */
    static createFrom(data, result) {
        if (data instanceof Float64Array) {
            if (data.length >= 2)
                return Vector2d.create(data[0], data[1]);
            if (data.length >= 1)
                return Vector2d.create(data[0], 0);
            return Vector2d.create(0, 0);
        }
        return Vector2d.create(data.x, data.y, result);
    }
    /**
     * Set x and y from a JSON input such as `[1,2]` or `{x:1, y:2}`
     * * If no JSON input is provided, 0 would be used as default values for x and y.
     * @param json the JSON input
     */
    static fromJSON(json) {
        const val = new Vector2d();
        val.setFromJSON(json);
        return val;
    }
    /** Return a new Vector2d from polar coordinates for radius and Angle from x axis */
    static createPolar(r, theta) {
        return Vector2d.create(r * theta.cos(), r * theta.sin());
    }
    /** Return a new Vector2d extending from point0 to point1 */
    static createStartEnd(point0, point1, result) {
        return Vector2d.create(point1.x - point0.x, point1.y - point0.y, result);
    }
    /**
     * Return a vector that bisects the angle between two normals and extends to the intersection of two offset lines
     * * returns `undefined` if `unitPerpA = -unitPerpB` (i.e., are opposite)
     * * math details can be found at docs/learning/geometry/PointVector.md
     * @param unitPerpA unit perpendicular to incoming direction
     * @param unitPerpB  unit perpendicular to outgoing direction
     * @param offset offset distance
     */
    static createOffsetBisector(unitPerpA, unitPerpB, offset) {
        let bisector = unitPerpA.plus(unitPerpB);
        bisector = bisector.normalize();
        if (bisector) {
            const c = bisector.dotProduct(unitPerpA);
            bisector.scale(offset, bisector);
            return bisector.safeDivideOrNull(c);
        }
        return undefined;
    }
    /** Return strongly typed Vector2d sum: `vectorA * scaleA + vectorB * scaleB`. */
    static createAdd2Scaled(vectorA, scaleA, vectorB, scaleB, result) {
        return Vector2d.create(vectorA.x * scaleA + vectorB.x * scaleB, vectorA.y * scaleA + vectorB.y * scaleB, result);
    }
    /** Return strongly typed Vector2d sum: `vectorA * scaleA + vectorB * scaleB` with separate x,y components. */
    static createAdd2ScaledXY(ax, ay, scaleA, bx, by, scaleB, result) {
        return Vector2d.create(ax * scaleA + bx * scaleB, ay * scaleA + by * scaleB, result);
    }
    /** Return strongly typed Vector2d sum: `vectorA * scaleA + vectorB * scaleB + vectorC * scaleC`. */
    static createAdd3Scaled(vectorA, scaleA, vectorB, scaleB, vectorC, scaleC, result) {
        return Vector2d.create(vectorA.x * scaleA + vectorB.x * scaleB + vectorC.x * scaleC, vectorA.y * scaleA + vectorB.y * scaleB + vectorC.y * scaleC, result);
    }
    /** Return strongly typed Vector2d sum: `vectorA * scaleA + vectorB * scaleB + vectorC * scaleC` with separate x,y components. */
    static createAdd3ScaledXY(ax, ay, scaleA, bx, by, scaleB, cx, cy, scaleC, result) {
        return Vector2d.create(ax * scaleA + bx * scaleB + cx * scaleC, ay * scaleA + by * scaleB + cy * scaleC, result);
    }
    /**
     * Return a (new or optionally reused) vector which is `this` divided by `denominator`
     * * return undefined if denominator is zero.
     */
    safeDivideOrNull(denominator, result) {
        if (denominator !== 0.0) {
            return this.scale(1.0 / denominator, result);
        }
        return undefined;
    }
    /** Return a unit vector in direction of this instance (undefined if this instance has near zero length) */
    normalize(result) {
        const mag = Geometry.correctSmallFraction(this.magnitude());
        result = result ? result : new Vector2d();
        return this.safeDivideOrNull(mag, result);
    }
    /**
     * Return fractional length of the projection of the instance onto the target vector.
     * @param target the target vector
     * @param defaultFraction the returned value in case the magnitude of `target` is too small
     * @returns the signed length of the projection divided by the length of `target`
     */
    fractionOfProjectionToVector(target, defaultFraction) {
        /*
         * projection length is (this.target)/||target||
         * but here we return (this.target)/||target||^2
         */
        const denominator = target.magnitudeSquared();
        if (denominator < Geometry.smallMetricDistanceSquared)
            return defaultFraction ? defaultFraction : 0;
        const numerator = this.dotProduct(target);
        return numerator / denominator;
    }
    /** Return a new vector with components negated from this instance. */
    negate(result) {
        result = result ? result : new Vector2d();
        result.x = -this.x;
        result.y = -this.y;
        return result;
    }
    /** Return a vector same length as this but rotated 90 degrees counter clockwise */
    rotate90CCWXY(result) {
        result = result ? result : new Vector2d();
        // save x,y to allow aliasing ("this" can be passed to the function as "result")
        const xx = this.x;
        const yy = this.y;
        result.x = -yy;
        result.y = xx;
        return result;
    }
    /** Return a vector same length as this but rotated 90 degrees clockwise */
    rotate90CWXY(result) {
        result = result ? result : new Vector2d();
        // save x,y to allow aliasing ("this" can be passed to the function as "result")
        const xx = this.x;
        const yy = this.y;
        result.x = yy;
        result.y = -xx;
        return result;
    }
    /** Return a unit vector perpendicular to this instance. */
    unitPerpendicularXY(result) {
        result = result ? result : new Vector2d();
        const xx = this.x;
        const yy = this.y;
        // save x,y to allow aliasing ("this" can be passed to the function as "result")
        result.x = -yy;
        result.y = xx;
        const d2 = xx * xx + yy * yy;
        if (d2 !== 0.0) {
            const a = 1.0 / Math.sqrt(d2);
            result.x *= a;
            result.y *= a;
        }
        return result;
    }
    /** Return a new Vector2d rotated CCW by given angle */
    rotateXY(angle, result) {
        const s = angle.sin();
        const c = angle.cos();
        // save x,y to allow aliasing ("this" can be passed to the function as "result")
        const xx = this.x;
        const yy = this.y;
        result = result ? result : new Vector2d();
        result.x = xx * c - yy * s;
        result.y = xx * s + yy * c;
        return result;
    }
    /**
     * Return a vector computed at fractional position between this vector and vectorB
    * @param fraction fractional position.  0 is at `this`.  1 is at `vectorB`.
    *                 True fractions are "between", negatives are "before this", beyond 1 is "beyond vectorB".
    * @param vectorB second vector
    * @param result optional preallocated result.
    */
    interpolate(fraction, vectorB, result) {
        result = result ? result : new Vector2d();
        /*
         * For best last-bit behavior, if fraction is below 0.5, use this as base point.
         * If above 0.5, use vectorB as base point.
         */
        if (fraction <= 0.5) {
            result.x = this.x + fraction * (vectorB.x - this.x);
            result.y = this.y + fraction * (vectorB.y - this.y);
        }
        else {
            const t = fraction - 1.0;
            result.x = vectorB.x + t * (vectorB.x - this.x);
            result.y = vectorB.y + t * (vectorB.y - this.y);
        }
        return result;
    }
    /** Return {this + vector}. */
    plus(vector, result) {
        result = result ? result : new Vector2d();
        result.x = this.x + vector.x;
        result.y = this.y + vector.y;
        return result;
    }
    /** Return {this - vector}. */
    minus(vector, result) {
        result = result ? result : new Vector2d();
        result.x = this.x - vector.x;
        result.y = this.y - vector.y;
        return result;
    }
    /** Return {point + vector \* scalar} */
    plusScaled(vector, scaleFactor, result) {
        result = result ? result : new Vector2d();
        result.x = this.x + vector.x * scaleFactor;
        result.y = this.y + vector.y * scaleFactor;
        return result;
    }
    /** Return {point + vectorA \* scalarA + vectorB \* scalarB} */
    plus2Scaled(vectorA, scalarA, vectorB, scalarB, result) {
        result = result ? result : new Vector2d();
        result.x = this.x + vectorA.x * scalarA + vectorB.x * scalarB;
        result.y = this.y + vectorA.y * scalarA + vectorB.y * scalarB;
        return result;
    }
    /** Return {this + vectorA \* scalarA + vectorB \* scalarB + vectorC \* scalarC} */
    plus3Scaled(vectorA, scalarA, vectorB, scalarB, vectorC, scalarC, result) {
        result = result ? result : new Vector2d();
        result.x = this.x + vectorA.x * scalarA + vectorB.x * scalarB + vectorC.x * scalarC;
        result.y = this.y + vectorA.y * scalarA + vectorB.y * scalarB + vectorC.y * scalarC;
        return result;
    }
    /** Return {this * scale} */
    scale(scale, result) {
        result = result ? result : new Vector2d();
        result.x = this.x * scale;
        result.y = this.y * scale;
        return result;
    }
    /** Return a vector parallel to this but with specified length */
    scaleToLength(length, result) {
        const mag = Geometry.correctSmallFraction(this.magnitude());
        if (mag === 0)
            return undefined;
        return this.scale(length / mag, result);
    }
    /** Return the dot product of this with vectorB */
    dotProduct(vectorB) {
        return this.x * vectorB.x + this.y * vectorB.y;
    }
    /** Dot product with vector from pointA to pointB */
    dotProductStartEnd(pointA, pointB) {
        return this.x * (pointB.x - pointA.x) + this.y * (pointB.y - pointA.y);
    }
    /** Vector cross product {this CROSS vectorB} */
    crossProduct(vectorB) {
        return this.x * vectorB.y - this.y * vectorB.x;
    }
    /**
     * Return the radians (as a simple number, not strongly typed Angle) signed angle from this to vectorB.
     * This is positive if the shortest turn is counterclockwise, negative if clockwise.
     */
    radiansTo(vectorB) {
        return Math.atan2(this.crossProduct(vectorB), this.dotProduct(vectorB));
    }
    /**
     * Return the (strongly typed) signed angle from this to vectorB.
     * This is positive if the shortest turn is counterclockwise, negative if clockwise.
     */
    angleTo(vectorB) {
        return Angle.createRadians(this.radiansTo(vectorB));
    }
    /**
     * Test if this vector is parallel to other.
     * * The input tolerances in `options`, if given, are considered to be squared for efficiency's sake,
     * so if you have a distance or angle tolerance t, you should pass in t * t.
     * @param other second vector for comparison.
     * @param oppositeIsParallel whether to consider diametrically opposed vectors as parallel.
     * @param options optional radian and distance tolerances.
     */
    isParallelTo(other, oppositeIsParallel = false, returnValueIfAnInputIsZeroLength = false, options) {
        const radianSquaredTol = options?.radianSquaredTol ?? Geometry.smallAngleRadiansSquared;
        const distanceSquaredTol = options?.distanceSquaredTol ?? Geometry.smallMetricDistanceSquared;
        const a2 = this.magnitudeSquared();
        const b2 = other.magnitudeSquared();
        if (a2 < distanceSquaredTol || b2 < distanceSquaredTol)
            return returnValueIfAnInputIsZeroLength;
        const dot = this.dotProduct(other);
        if (dot < 0.0 && !oppositeIsParallel)
            return false;
        const cross = this.crossProduct(other);
        /* a2,b2,cross2 are squared lengths of respective vectors */
        /* cross2 = sin^2(theta) * a2 * b2 */
        /* For small theta, sin^2(theta)~~theta^2 */
        return cross * cross <= radianSquaredTol * a2 * b2;
    }
    /**
     * Test if this vector is perpendicular to other.
     * * The input tolerances in `options`, if given, are considered to be squared for efficiency's sake,
     * so if you have a distance or angle tolerance t, you should pass in t * t.
     * @param other second vector in comparison.
     * @param returnValueIfAnInputIsZeroLength if either vector is near zero length, return this value.
     * @param options optional radian and distance tolerances.
     */
    isPerpendicularTo(other, returnValueIfAnInputIsZeroLength = false, options) {
        const radianSquaredTol = options?.radianSquaredTol ?? Geometry.smallAngleRadiansSquared;
        const distanceSquaredTol = options?.distanceSquaredTol ?? Geometry.smallMetricDistanceSquared;
        const aa = this.magnitudeSquared();
        const bb = other.magnitudeSquared();
        if (aa < distanceSquaredTol || bb < distanceSquaredTol)
            return returnValueIfAnInputIsZeroLength;
        const ab = this.dotProduct(other);
        return ab * ab <= radianSquaredTol * aa * bb;
    }
}
//# sourceMappingURL=Point2dVector2d.js.map