"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.LineSegment3d = void 0;
/** @packageDocumentation
 * @module Curve
 */
const core_bentley_1 = require("@itwin/core-bentley");
const Geometry_1 = require("../Geometry");
const Plane3dByOriginAndVectors_1 = require("../geometry3d/Plane3dByOriginAndVectors");
const Point2dVector2d_1 = require("../geometry3d/Point2dVector2d");
const Point3dVector3d_1 = require("../geometry3d/Point3dVector3d");
const Range_1 = require("../geometry3d/Range");
const Ray3d_1 = require("../geometry3d/Ray3d");
const BezierPolynomials_1 = require("../numerics/BezierPolynomials");
const SmallSystem_1 = require("../numerics/SmallSystem");
const CurveExtendMode_1 = require("./CurveExtendMode");
const CurveLocationDetail_1 = require("./CurveLocationDetail");
const CurvePrimitive_1 = require("./CurvePrimitive");
const PlaneAltitudeRangeContext_1 = require("./internalContexts/PlaneAltitudeRangeContext");
const OffsetOptions_1 = require("./OffsetOptions");
/**
 * A LineSegment3d is:
 * * A 3d line segment represented by its start and end coordinates
 *   * startPoint
 *   * endPoint
 * * The segment is parameterized with fraction 0 at the start and fraction 1 at the end, i.e. each of these
 * equivalent forms maps fraction `f` to a point `X(f)`:
 * ```
 * equation
 *  X(f) = P_0 + f*(P_1 - P_0)\newline
 *  X(f) = (1-f)*P_0 + f*P_1
 * ```
 * @public
 */
class LineSegment3d extends CurvePrimitive_1.CurvePrimitive {
    /** String name for schema properties */
    curvePrimitiveType = "lineSegment";
    /** Test if `other` is of class `LineSegment3d` */
    isSameGeometryClass(other) {
        return other instanceof LineSegment3d;
    }
    /** Start point of the segment */
    _point0;
    /** End point of the segment */
    _point1;
    /**
     * Return REFERENCE to the start point of this segment.
     * * This is distinct from the `CurvePrimitive` abstract method `startPoint()` which creates a returned point.
     */
    get point0Ref() {
        return this._point0;
    }
    /**
     * Return REFERENCE to the end point of this segment.
     * * This is distinct from the `CurvePrimitive` abstract method `endPoint()` which creates a returned point.
     */
    get point1Ref() {
        return this._point1;
    }
    /** Return a copy of the start and end points in an array. */
    get points() {
        return [this._point0.clone(), this._point1.clone()];
    }
    /** A LineSegment3d extends along its infinite line. */
    get isExtensibleFractionSpace() {
        return true;
    }
    /**
     * CAPTURE point references as a `LineSegment3d`
     * @param point0
     * @param point1
     */
    constructor(point0, point1) {
        super();
        this._point0 = point0;
        this._point1 = point1;
    }
    /** Set the start and endpoints by capturing input references. */
    setRefs(point0, point1) {
        this._point0 = point0;
        this._point1 = point1;
    }
    /** Set the start and endpoints by cloning the input parameters. */
    set(point0, point1) {
        this._point0 = point0.clone();
        this._point1 = point1.clone();
    }
    /** Copy (clone) data from other */
    setFrom(other) {
        this._point0.setFrom(other._point0);
        this._point1.setFrom(other._point1);
    }
    /** Return a (clone of) the start point (This is NOT a reference to the stored start point) */
    startPoint(result) {
        if (result) {
            result.setFrom(this._point0);
            return result;
        }
        return this._point0.clone();
    }
    /** Return a (clone of) the end point (This is NOT a reference to the stored end point) */
    endPoint(result) {
        if (result) {
            result.setFrom(this._point1);
            return result;
        }
        return this._point1.clone();
    }
    /** Return the point and derivative vector at fractional position along the line segment. */
    fractionToPointAndDerivative(fraction, result) {
        result = result ? result : Ray3d_1.Ray3d.createZero();
        result.direction.setStartEnd(this._point0, this._point1);
        this._point0.interpolate(fraction, this._point1, result.origin);
        return result;
    }
    /**
     * Construct a plane with
     * * origin at the fractional position along the line segment
     * * x axis is the first derivative, i.e. along the line segment
     * * y axis is the second derivative, i.e. 000
     */
    fractionToPointAnd2Derivatives(fraction, result) {
        result = result ? result : Plane3dByOriginAndVectors_1.Plane3dByOriginAndVectors.createXYPlane();
        result.vectorU.setStartEnd(this._point0, this._point1);
        result.vectorV.set(0, 0, 0);
        this._point0.interpolate(fraction, this._point1, result.origin);
        return result;
    }
    /** Clone the LineSegment3d */
    clone() {
        return LineSegment3d.create(this._point0, this._point1);
    }
    /** Clone and apply transform to the clone. */
    cloneTransformed(transform) {
        const c = this.clone();
        c.tryTransformInPlace(transform);
        return c;
    }
    /** Create with start and end points.  The point contents are cloned into the LineSegment3d. */
    static create(point0, point1, result) {
        if (result) {
            result.set(point0, point1); // and this will clone them !!
            return result;
        }
        return new LineSegment3d(point0.clone(), point1.clone());
    }
    /** Create with start and end points.  The point contents are CAPTURED into the result */
    static createCapture(point0, point1) {
        return new LineSegment3d(point0, point1);
    }
    /**
     * Create a LineSegment3d from xy coordinates of start and end, with common z.
     * @param x0 start point x coordinate.
     * @param y0 start point y coordinate.
     * @param x1 end point x coordinate.
     * @param y1 end point y coordinate.
     * @param z z coordinate to use for both points.
     * @param result optional existing LineSegment to be reinitialized.
     */
    static createXYXY(x0, y0, x1, y1, z = 0, result) {
        if (result) {
            result._point0.set(x0, y0, z);
            result._point1.set(x1, y1, z);
            return result;
        }
        return new LineSegment3d(Point3dVector3d_1.Point3d.create(x0, y0, z), Point3dVector3d_1.Point3d.create(x1, y1, z));
    }
    /**
     * Create a LineSegment3d from xy coordinates of start and end, with common z.
     * @param x0 start point x coordinate.
     * @param y0 start point y coordinate.
     * @param x1 end point x coordinate.
     * @param y1 end point y coordinate.
     * @param z z coordinate to use for both points.
     * @param result optional existing LineSegment to be reinitialized.
     */
    static createXYZXYZ(x0, y0, z0, x1, y1, z1, result) {
        if (result) {
            result._point0.set(x0, y0, z0);
            result._point1.set(x1, y1, z1);
            return result;
        }
        return new LineSegment3d(Point3dVector3d_1.Point3d.create(x0, y0, z0), Point3dVector3d_1.Point3d.create(x1, y1, z1));
    }
    /** Return the point at fractional position along the line segment. */
    fractionToPoint(fraction, result) {
        return this._point0.interpolate(fraction, this._point1, result);
    }
    /** Return the length of the segment. */
    curveLength() {
        return this._point0.distance(this._point1);
    }
    /** Return the length of the partial segment between fractions. */
    curveLengthBetweenFractions(fraction0, fraction1) {
        return Math.abs(fraction1 - fraction0) * this._point0.distance(this._point1);
    }
    /** Return the length of the segment. */
    quickLength() {
        return this.curveLength();
    }
    /**
     * Returns a curve location detail with both xyz and fractional coordinates of the closest point.
     * @param spacePoint point in space
     * @param extend if false, only return points within the bounded line segment. If true, allow the point to be on
     * the unbounded line that contains the bounded segment.
     * @param result optional pre-allocated object to populate and return
     * @returns detail, with `a` field set to the distance from `spacePoint` to the closest point
     */
    closestPoint(spacePoint, extend, result) {
        let fraction = spacePoint.fractionOfProjectionToLine(this._point0, this._point1, 0.0);
        fraction = CurveExtendMode_1.CurveExtendOptions.correctFraction(extend, fraction);
        result = CurveLocationDetail_1.CurveLocationDetail.create(this, result);
        result.fraction = fraction;
        this._point0.interpolate(fraction, this._point1, result.point);
        result.vectorInCurveLocationDetail = undefined;
        result.a = result.point.distance(spacePoint);
        return result;
    }
    /**
     * Compute the closest approach between a pair of line segments.
     * * The approach distance is returned in the `a` fields of the details.
     * @param segmentA first line segment
     * @param extendA how to extend segmentA forward/backward
     * @param segmentB second line segment
     * @param extendB how to extend segmentB forward/backward
     * @param result optional pre-allocated object to populate and return
     * @returns pair of details, one per segment, each with `a` field set to the closest approach distance
     */
    static closestApproach(segmentA, extendA, segmentB, extendB, result) {
        const unboundedFractions = Point2dVector2d_1.Vector2d.create();
        if (result === undefined)
            result = CurveLocationDetail_1.CurveLocationDetailPair.createCapture(CurveLocationDetail_1.CurveLocationDetail.create(), CurveLocationDetail_1.CurveLocationDetail.create());
        if (SmallSystem_1.SmallSystem.lineSegment3dClosestApproachUnbounded(segmentA._point0, segmentA._point1, segmentB._point0, segmentB._point1, unboundedFractions)) {
            // There is a simple approach between the unbounded segments.  Maybe its a really easy case ...
            const fractionA = CurveExtendMode_1.CurveExtendOptions.correctFraction(extendA, unboundedFractions.x);
            const fractionB = CurveExtendMode_1.CurveExtendOptions.correctFraction(extendB, unboundedFractions.y);
            // if neither fraction was corrected, just accept !!!
            if (fractionA === unboundedFractions.x && fractionB === unboundedFractions.y) {
                CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFraction(segmentA, fractionA, result.detailA);
                CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFraction(segmentB, fractionB, result.detailB);
                result.detailA.a = result.detailB.a = result.detailA.point.distance(result.detailB.point);
                return result;
            }
            // One or both of the fractions were clamped back to an endpoint.
            // Claim: (????!!!????) The only proximity candidates that matter are from clamped point onto the other.
            if (fractionA !== unboundedFractions.x && fractionB !== unboundedFractions.y) {
                // Fill in (in the result) both individual details with "projected" points and distance.
                // The "loser" will have its contents replaced.
                const clampedPointOnA = fractionA < 0.5 ? segmentA._point0 : segmentA._point1;
                const clampedPointOnB = fractionB < 0.5 ? segmentB._point0 : segmentB._point1;
                segmentB.closestPoint(clampedPointOnA, extendB, result.detailB);
                segmentA.closestPoint(clampedPointOnB, extendA, result.detailA);
                if (result.detailA.a <= result.detailB.a) {
                    CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(segmentB, fractionB, clampedPointOnB, result.detailB);
                }
                else {
                    CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(segmentA, fractionA, clampedPointOnA, result.detailA);
                }
            }
            else if (fractionB !== unboundedFractions.y) {
                // B (only) was clamped.
                const clampedPointOnB = fractionB < 0.5 ? segmentB._point0 : segmentB._point1;
                segmentA.closestPoint(clampedPointOnB, extendA, result.detailA);
                result.detailB.setCurve(segmentB);
                result.detailB.point.setFrom(clampedPointOnB);
                result.detailB.fraction = fractionB;
            }
            else {
                // fractionA was clamped.
                const clampedPointOnA = fractionA < 0.5 ? segmentA._point0 : segmentA._point1;
                segmentB.closestPoint(clampedPointOnA, extendB, result.detailB);
                result.detailA.setCurve(segmentA);
                result.detailA.point.setFrom(clampedPointOnA);
                result.detailA.fraction = fractionA;
            }
            result.detailA.a = result.detailB.a = result.detailA.point.distance(result.detailB.point);
            return result;
        }
        // (probably? certainly?) parallel (possibly coincident) lines.
        // run all 4 endpoint-to-other cases . . . reassemble carefully ...
        const resultSet = [
            segmentA.closestPoint(segmentB._point0, extendA),
            segmentA.closestPoint(segmentB._point1, extendA),
            segmentB.closestPoint(segmentA._point0, extendB),
            segmentB.closestPoint(segmentA._point1, extendB),
        ];
        let dMin = resultSet[0].a;
        let iMin = 0;
        for (let i = 1; i < 4; i++) {
            if (resultSet[i].a < dMin) {
                iMin = i;
                dMin = resultSet[i].a;
            }
        }
        if (iMin === 0) {
            resultSet[0].clone(result.detailA);
            CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFraction(segmentB, 0.0, result.detailB);
            result.detailB.a = result.detailA.a;
        }
        else if (iMin === 1) {
            resultSet[1].clone(result.detailA);
            CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFraction(segmentB, 1.0, result.detailB);
            result.detailB.a = result.detailA.a;
        }
        else if (iMin === 2) {
            resultSet[2].clone(result.detailB);
            CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFraction(segmentA, 0.0, result.detailA);
            result.detailA.a = result.detailB.a;
        }
        else {
            (0, core_bentley_1.assert)(iMin === 3);
            resultSet[3].clone(result.detailB);
            CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFraction(segmentA, 1.0, result.detailA);
            result.detailA.a = result.detailB.a;
        }
        return result;
    }
    /** Swap the endpoint references. */
    reverseInPlace() {
        const a = this._point0;
        this._point0 = this._point1;
        this._point1 = a;
    }
    /** Transform the two endpoints of this LinSegment. */
    tryTransformInPlace(transform) {
        this._point0 = transform.multiplyPoint3d(this._point0, this._point0);
        this._point1 = transform.multiplyPoint3d(this._point1, this._point1);
        return true;
    }
    /** Test if both endpoints are in a plane (within tolerance) */
    isInPlane(plane) {
        return Geometry_1.Geometry.isSmallMetricDistance(plane.altitude(this._point0))
            && Geometry_1.Geometry.isSmallMetricDistance(plane.altitude(this._point1));
    }
    /**
     * Compute points of simple (transverse) with a plane.
     * * Use isInPlane to test if the line segment is completely in the plane.
     */
    appendPlaneIntersectionPoints(plane, result) {
        const h0 = plane.altitude(this._point0);
        const h1 = plane.altitude(this._point1);
        const fraction = BezierPolynomials_1.Order2Bezier.solveCoffs(h0, h1);
        let numIntersection = 0;
        if (fraction !== undefined) {
            numIntersection++;
            const detail = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(this, fraction, this.fractionToPoint(fraction));
            detail.intervalRole = CurveLocationDetail_1.CurveIntervalRole.isolated;
            result.push(detail);
        }
        return numIntersection;
    }
    /**
     * Extend a range to include the (optionally transformed) line segment
     * @param range range to extend
     * @param transform optional transform to apply to the end points
     */
    extendRange(range, transform) {
        if (transform) {
            range.extendTransformedPoint(transform, this._point0);
            range.extendTransformedPoint(transform, this._point1);
        }
        else {
            range.extendPoint(this._point0);
            range.extendPoint(this._point1);
        }
    }
    /**
     * Construct a line from either of these json forms:
     *
     * * object with named start and end:
     * `{startPoint: pointValue, endPoint: pointValue}`
     * * array of two point values:
     * `[pointValue, pointValue]`
     * The point values are any values accepted by the Point3d method setFromJSON.
     * @param json data to parse.
     */
    setFromJSON(json) {
        if (!json) {
            this._point0.set(0, 0, 0);
            this._point1.set(1, 0, 0);
            return;
        }
        else if (json.startPoint && json.endPoint) { // {startPoint:json point, endPoint:json point}
            this._point0.setFromJSON(json.startPoint);
            this._point1.setFromJSON(json.endPoint);
        }
        else if (Array.isArray(json)
            && json.length > 1) { // [json point, json point]
            this._point0.setFromJSON(json[0]);
            this._point1.setFromJSON(json[1]);
        }
    }
    /** A simple line segment's fraction and distance are proportional. */
    getFractionToDistanceScale() {
        return this.curveLength();
    }
    /**
     * Place the lineSegment3d start and points in a json object
     * @return {*} [[x,y,z],[x,y,z]]
     */
    toJSON() {
        return [this._point0.toJSON(), this._point1.toJSON()];
    }
    /** Create a new `LineSegment3d` with coordinates from json object.   See `setFromJSON` for object layout description. */
    static fromJSON(json) {
        const result = new LineSegment3d(Point3dVector3d_1.Point3d.createZero(), Point3dVector3d_1.Point3d.create());
        result.setFromJSON(json);
        return result;
    }
    /** Near equality test with `other`. */
    isAlmostEqual(other) {
        if (other instanceof LineSegment3d) {
            const ls = other;
            return this._point0.isAlmostEqual(ls._point0) && this._point1.isAlmostEqual(ls._point1);
        }
        return false;
    }
    /** Emit strokes to caller-supplied linestring */
    emitStrokes(dest, options) {
        const numStroke = this.computeStrokeCountForOptions(options);
        dest.appendFractionalStrokePoints(this, numStroke, 0.0, 1.0);
    }
    /** Emit strokes to caller-supplied handler */
    emitStrokableParts(handler, options) {
        handler.startCurvePrimitive(this);
        const numStroke = this.computeStrokeCountForOptions(options);
        handler.announceSegmentInterval(this, this._point0, this._point1, numStroke, 0.0, 1.0);
        handler.endCurvePrimitive(this);
    }
    /**
     * Return the stroke count required for given options.
     * @param options StrokeOptions that determine count
     */
    computeStrokeCountForOptions(options) {
        let numStroke = 1;
        if (options) {
            if (options.maxEdgeLength)
                numStroke = options.applyMaxEdgeLength(numStroke, this.curveLength());
            numStroke = options.applyMinStrokesPerPrimitive(numStroke);
        }
        return numStroke;
    }
    /** Second step of double dispatch: call `handler.handleLineSegment3d(this)` */
    dispatchToGeometryHandler(handler) {
        return handler.handleLineSegment3d(this);
    }
    /**
     * Find intervals of this curve primitive that are interior to a clipper
     * @param clipper clip structure (e.g. clip planes)
     * @param announce function to be called announcing fractional intervals `announce(fraction0, fraction1, curvePrimitive)`
     */
    announceClipIntervals(clipper, announce) {
        return clipper.announceClippedSegmentIntervals(0.0, 1.0, this._point0, this._point1, announce ? (fraction0, fraction1) => announce(fraction0, fraction1, this) : undefined);
    }
    /**
     * Return (if possible) a curve primitive which is a portion of this curve.
     * @param fractionA [in] start fraction
     * @param fractionB [in] end fraction
     */
    clonePartialCurve(fractionA, fractionB) {
        return LineSegment3d.create(this.fractionToPoint(fractionA), this.fractionToPoint(fractionB));
    }
    /**
     * Returns a (high accuracy) range of the curve between fractional positions
     * * Default implementation returns teh range of the curve from clonePartialCurve
     */
    rangeBetweenFractions(fraction0, fraction1, transform) {
        // (This is cheap -- don't bother testing for fraction0===fraction1)
        if (!transform) {
            const range = Range_1.Range3d.create();
            range.extendInterpolated(this._point0, fraction0, this._point1);
            range.extendInterpolated(this._point0, fraction1, this._point1);
            return range;
        }
        const point0 = this.fractionToPoint(fraction0);
        const point1 = this.fractionToPoint(fraction1);
        if (transform) {
            transform.multiplyPoint3d(point0, point0);
            transform.multiplyPoint3d(point1, point1);
        }
        return Range_1.Range3d.create(point0, point1);
    }
    /**
     * Construct an offset of the instance curve as viewed in the xy-plane (ignoring z).
     * @param offsetDistanceOrOptions offset distance (positive to left of the instance curve), or options object
     */
    constructOffsetXY(offsetDistanceOrOptions) {
        const offsetVec = Point3dVector3d_1.Vector3d.createStartEnd(this._point0, this._point1);
        if (offsetVec.normalizeInPlace()) {
            offsetVec.rotate90CCWXY(offsetVec);
            const offsetDist = OffsetOptions_1.OffsetOptions.getOffsetDistance(offsetDistanceOrOptions);
            return LineSegment3d.create(this._point0.plusScaled(offsetVec, offsetDist), this._point1.plusScaled(offsetVec, offsetDist));
        }
        return undefined;
    }
    /**
     * Project instance geometry (via dispatch) onto the given ray, and return the extreme fractional parameters of
     * projection.
     * @param ray ray onto which the instance is projected. A `Vector3d` is treated as a `Ray3d` with zero origin.
     * @param lowHigh optional receiver for output
     * @returns range of fractional projection parameters onto the ray, where 0.0 is start of the ray and 1.0 is the
     * end of the ray.
     */
    projectedParameterRange(ray, lowHigh) {
        return PlaneAltitudeRangeContext_1.PlaneAltitudeRangeContext.findExtremeFractionsAlongDirection(this, ray, lowHigh);
    }
}
exports.LineSegment3d = LineSegment3d;
//# sourceMappingURL=LineSegment3d.js.map