"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Segment1d = void 0;
/** @packageDocumentation
 * @module CartesianGeometry
 */
const Geometry_1 = require("../Geometry");
/**
 * * A Segment1d is an interval of an axis named x.
 * * The interval is defined by two values x0 and x1.
 * * The x0 and x1 values can be in either order.
 *   * if `x0 < x1` fractional coordinates within the segment move from left to right.
 *   * if `x0 > x1` fractional coordinates within the segment move from right to left.
 * * This differs from a Range1d in that:
 * * For a Range1d the reversed ordering of its limit values means "empty interval".
 * * For a Segment1d the reversed ordering is a real interval but fractional positions move backwards.
 * * The segment is parameterized with a fraction
 * * * Fraction 0 is the start (`x0`)
 * * * Fraction 1 is the end (`x1`)
 * * * The fraction equation is `x = x0 + fraction * (x1-x0)` or (equivalently) `x = (1-fraction) * x0 + fraction * x1`
 * @public
 */
class Segment1d {
    /** start coordinate */
    x0;
    /** end coordinate */
    x1;
    constructor(x0, x1) {
        this.x0 = x0;
        this.x1 = x1;
    }
    /**
     * replace both end values.
     * @param x0 new x0 value
     * @param x1 new y0 value
     */
    set(x0, x1) { this.x0 = x0, this.x1 = x1; }
    /**
     * shift (translate) the segment along its axis by adding `dx` to both `x0` and `x1`.
     * @param dx value to add to both x0 and x1
     */
    shift(dx) { this.x0 += dx, this.x1 += dx; }
    /**
     * create segment1d with given end values
     * @param x0 start value
     * @param x1 end value
     * @param result optional pre-existing result to be reinitialized.
     */
    static create(x0 = 0, x1 = 1, result) {
        if (!result)
            return new Segment1d(x0, x1);
        result.set(x0, x1);
        return result;
    }
    /**
     * Copy both end values from other Segment1d
     * @param other source Segment1d
     */
    setFrom(other) { this.x0 = other.x0; this.x1 = other.x1; }
    /**
     * clone this Segment1d, return as a separate object.
     */
    clone() { return new Segment1d(this.x0, this.x1); }
    /**
     * Returns true if both coordinates (`x0` and `x1`) are in the 0..1 range.
     */
    get isIn01() {
        return Geometry_1.Geometry.isIn01(this.x0) && Geometry_1.Geometry.isIn01(this.x1);
    }
    /**
     * Evaluate the segment at fractional position
     * @returns position within the segment
     * @param fraction fractional position within this segment
     */
    fractionToPoint(fraction) { return Geometry_1.Geometry.interpolate(this.x0, fraction, this.x1); }
    /**
     * Return the signed start-to-end shift (aka signed distance)
     */
    signedDelta() { return this.x1 - this.x0; }
    /**
     * Return the absolute start-to-end shift (aka distance)
     */
    absoluteDelta() { return Math.abs(this.x1 - this.x0); }
    /**
     * * swap the x0 and x1 member values.
     * * This makes the fractionToPoint evaluates reverse direction.
     */
    reverseInPlace() { const x = this.x0; this.x0 = this.x1; this.x1 = x; }
    /**
     * * if `x1-x0` multiplied by the scale factor is (strictly) negative, swap the x0 and x1 member values.
     * * This makes the fractionToPoint evaluates reverse direction.
     */
    reverseIfNeededForDeltaSign(sign = 1) {
        if (sign * (this.x1 - this.x0) < 0)
            this.reverseInPlace();
    }
    /**
     * Near equality test, using Geometry.isSameCoordinate for tolerances.
     */
    isAlmostEqual(other) {
        return Geometry_1.Geometry.isSameCoordinate(this.x0, other.x0) && Geometry_1.Geometry.isSameCoordinate(this.x1, other.x1);
    }
    /**
     * Return true if the segment limits are (exactly) 0 and 1
     */
    get isExact01() { return this.x0 === 0.0 && this.x1 === 1.0; }
    /**
     * Return true if the segment limits are (exactly) 1 and 0
     */
    get isExact01Reversed() { return this.x0 === 1.0 && this.x1 === 0.0; }
    /** On input, `this` is an interval of a line.  On output, the interval has been clipped to positive parts of a linear function
     * * f0 and f1 are values at parameter values 0 and 1 (which are in general NOT x0 and x1)
     * * From that determine where the segment crosses function value 0.
     * * The segment contains some interval in the same parameter space.
     * * Clip the segment to the positive part of the space.
     * * Return true (and modify the segment) if any of the segment remains.
     * * Return false (but without modifying the segment) if the active part is entirely out.
     */
    clipBy01FunctionValuesPositive(f0, f1) {
        const df01 = f1 - f0;
        const fA = f0 + this.x0 * df01;
        const fB = f0 + this.x1 * df01;
        const dfAB = fB - fA;
        if (fA > 0) {
            if (fB >= 0)
                return true; // inside at both ends
            /** There is an inside to outside crossing. The division is safe ... (and value between 0 and 1) */
            const u = -fA / dfAB;
            this.x1 = this.x0 + u * (this.x1 - this.x0);
            return true;
        }
        else if (fA < 0) {
            if (fB < 0)
                return false; // outside at both ends.
            /** There is an outside to inside crossing crossing. The division is safe ... (and value between 0 and 1) */
            const u = -fA / dfAB;
            this.x0 = this.x0 + u * (this.x1 - this.x0);
            return true;
        }
        /** fA is on the cut.   fB determines the entire segment. */
        return fB > 0;
    }
    /**
     * * On input, (f0,f1) is a (directed) segment.
     * * On output, it is restricted to (0,1) while maintaining direction
     * * If the clip leaves nothing, leave this segment alone and return false.
     * * If the clip leaves something, update this segment and return true.
     * @param clamp0 whether to clamp the smaller number to 0. Default value is true.
     * @param clamp1 whether to clamp the larger number to 1. Default value is true.
     * @param allowSingleton whether to return true for an interval clipped to a single number (x0 === x1). Default value is false.
     * @return whether this interval was clipped
     */
    clampDirectedTo01(clamp0 = true, clamp1 = true, allowSingleton = false) {
        let x0 = this.x0;
        let x1 = this.x1;
        if (x1 > x0) {
            if (x0 < 0 && clamp0)
                x0 = 0;
            if (x1 > 1 && clamp1)
                x1 = 1;
            if (x0 > x1 || (x0 === x1 && !allowSingleton))
                return false;
        }
        else {
            if (x0 > 1 && clamp1)
                x0 = 1;
            if (x1 < 0 && clamp0)
                x1 = 0;
            if (x0 < x1 || (x0 === x1 && !allowSingleton))
                return false;
        }
        this.set(x0, x1);
        return true;
    }
}
exports.Segment1d = Segment1d;
//# sourceMappingURL=Segment1d.js.map