"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.CurveFactory = exports.MiteredSweepOutputSelect = void 0;
/** @packageDocumentation
 * @module Curve
 */
const core_bentley_1 = require("@itwin/core-bentley");
const Geometry_1 = require("../Geometry");
const Angle_1 = require("../geometry3d/Angle");
const AngleSweep_1 = require("../geometry3d/AngleSweep");
const Matrix3d_1 = require("../geometry3d/Matrix3d");
const Point2dVector2d_1 = require("../geometry3d/Point2dVector2d");
const Point3dArrayCarrier_1 = require("../geometry3d/Point3dArrayCarrier");
const Point3dVector3d_1 = require("../geometry3d/Point3dVector3d");
const PolylineOps_1 = require("../geometry3d/PolylineOps");
const Ray3d_1 = require("../geometry3d/Ray3d");
const Segment1d_1 = require("../geometry3d/Segment1d");
const Transform_1 = require("../geometry3d/Transform");
const SmallSystem_1 = require("../numerics/SmallSystem");
const PolyfaceBuilder_1 = require("../polyface/PolyfaceBuilder");
const Cone_1 = require("../solid/Cone");
const RuledSweep_1 = require("../solid/RuledSweep");
const TorusPipe_1 = require("../solid/TorusPipe");
const Arc3d_1 = require("./Arc3d");
const CurveCollection_1 = require("./CurveCollection");
const CurvePrimitive_1 = require("./CurvePrimitive");
const GeometryQuery_1 = require("./GeometryQuery");
const LineSegment3d_1 = require("./LineSegment3d");
const LineString3d_1 = require("./LineString3d");
const Loop_1 = require("./Loop");
const Path_1 = require("./Path");
const RegionOps_1 = require("./RegionOps");
const IntegratedSpiral3d_1 = require("./spiral/IntegratedSpiral3d");
/**
 * Enumeration of geometric output for [CurveFactory.createMiteredSweepSections].
 * @public
 */
var MiteredSweepOutputSelect;
(function (MiteredSweepOutputSelect) {
    /** Output only the parallel arrays of planes and sections. */
    MiteredSweepOutputSelect[MiteredSweepOutputSelect["Sections"] = 0] = "Sections";
    /** Output planes and sections, as well as the assembled ruled sweep. */
    MiteredSweepOutputSelect[MiteredSweepOutputSelect["AlsoRuledSweep"] = 1] = "AlsoRuledSweep";
    /** Output planes and sections, as well as the assembled ruled sweep and its stroked mesh. */
    MiteredSweepOutputSelect[MiteredSweepOutputSelect["AlsoMesh"] = 2] = "AlsoMesh";
})(MiteredSweepOutputSelect || (exports.MiteredSweepOutputSelect = MiteredSweepOutputSelect = {}));
;
/**
 * The `CurveFactory` class contains methods for specialized curve constructions.
 * @public
 */
class CurveFactory {
    /** (Cautiously) construct and save a line segment between fractional positions. */
    static addPartialSegment(path, allowBackup, pointA, pointB, fraction0, fraction1) {
        if (allowBackup || (fraction1 > fraction0)) {
            if (pointA !== undefined && pointB !== undefined && !Geometry_1.Geometry.isAlmostEqualNumber(fraction0, fraction1))
                path.tryAddChild(LineSegment3d_1.LineSegment3d.create(pointA.interpolate(fraction0, pointB), pointA.interpolate(fraction1, pointB)));
        }
    }
    /**
     * Create a circular arc defined by start point, tangent at start point, and end point.
     * * The circular arc is swept from start to end toward direction of the `tangentAtStart`.
     * * If tangent is parallel to line segment from start to end, return `undefined`.
     */
    static createArcPointTangentPoint(start, tangentAtStart, end) {
        const ret = Arc3d_1.Arc3d.createCircularStartTangentEnd(start, tangentAtStart, end);
        if (ret instanceof Arc3d_1.Arc3d)
            return ret;
        else
            return undefined;
    }
    /**
     * Construct a sequence of alternating lines and arcs with the arcs creating tangent transition between consecutive edges.
     *  * If the radius parameter is a number, that radius is used throughout.
     *  * If the radius parameter is an array of numbers, `radius[i]` is applied at `point[i]`.
     *  * A zero radius for any point indicates to leave the as a simple corner.
     * @param points point source.
     * @param radius fillet radius or array of radii indexed to correspond to the points.
     * @param allowCuspOrOptions flag to allow cusps in output (default `true`), or a list of extended options.
     */
    static createFilletsInLineString(points, radius, allowCuspOrOptions = true) {
        if (Array.isArray(points))
            return this.createFilletsInLineString(new Point3dArrayCarrier_1.Point3dArrayCarrier(points), radius, allowCuspOrOptions);
        if (points instanceof LineString3d_1.LineString3d)
            return this.createFilletsInLineString(points.packedPoints, radius, allowCuspOrOptions);
        let allowCusp = true;
        let filletClosure = false;
        if (typeof allowCuspOrOptions === "boolean") {
            allowCusp = allowCuspOrOptions;
        }
        else {
            allowCusp = allowCuspOrOptions.allowCusp ?? true;
            filletClosure = allowCuspOrOptions.filletClosure ?? false;
        }
        let n = points.length;
        if (filletClosure && points.almostEqualIndexIndex(0, n - 1))
            n--; // ignore closure point
        if (n <= 1)
            return undefined;
        const pointA = Point3dVector3d_1.Point3d.create();
        const pointB = Point3dVector3d_1.Point3d.create();
        const pointC = Point3dVector3d_1.Point3d.create();
        const blendArray = [];
        // remark: n=2 and n=3 cases should fall out from loop logic
        for (let i = 0; i < n; i++) {
            let thisRadius = Math.abs(Array.isArray(radius) ? (i < radius.length ? radius[i] : 0) : radius);
            if (!Number.isFinite(thisRadius))
                thisRadius = 0;
            if (thisRadius === 0 || (!filletClosure && (i === 0 || i === n - 1))) {
                blendArray.push({ fraction10: 0, fraction12: 0, point: points.getPoint3dAtUncheckedPointIndex(i) });
            }
            else {
                points.getPoint3dAtUncheckedPointIndex(Geometry_1.Geometry.modulo(i - 1, n), pointA);
                points.getPoint3dAtUncheckedPointIndex(i, pointB);
                points.getPoint3dAtUncheckedPointIndex(Geometry_1.Geometry.modulo(i + 1, n), pointC);
                blendArray.push(Arc3d_1.Arc3d.createFilletArc(pointA, pointB, pointC, thisRadius));
            }
        }
        (0, core_bentley_1.assert)(blendArray.length === n);
        if (!allowCusp) {
            // suppress arcs that overlap a neighboring arc, or that consume the entire segment
            for (let i = 0; i < n; i++) {
                const bB = blendArray[i];
                if (!bB.arc)
                    continue;
                const bA = blendArray[Geometry_1.Geometry.modulo(i - 1, n)];
                const bC = blendArray[Geometry_1.Geometry.modulo(i + 1, n)];
                if (bB.fraction10 > 1 || bB.fraction12 > 1 || 1 - bB.fraction10 < bA.fraction12 || bB.fraction12 > 1 - bC.fraction10) {
                    bB.fraction10 = bB.fraction12 = 0;
                    bB.arc = undefined;
                }
            }
        }
        const path = Path_1.Path.create();
        for (let i = 0; i < n; i++) {
            const b0 = blendArray[i];
            path.tryAddChild(b0.arc);
            if (i + 1 < n || filletClosure) {
                const b1 = blendArray[Geometry_1.Geometry.modulo(i + 1, n)];
                this.addPartialSegment(path, allowCusp, b0.point, b1.point, b0.fraction12, 1 - b1.fraction10);
            }
        }
        return path;
    }
    /**
     * Create a `Loop` with given xy corners and fixed z.
     * * The corners always proceed counter clockwise from lower left.
     * * If the radius is too large for the outer rectangle size, it is reduced to half of the the smaller x or y size.
     */
    static createRectangleXY(x0, y0, x1, y1, z = 0, filletRadius) {
        let radius = Geometry_1.Geometry.correctSmallMetricDistance(filletRadius);
        const xMin = Math.min(x0, x1);
        const xMax = Math.max(x0, x1);
        const yMin = Math.min(y0, y1);
        const yMax = Math.max(y0, y1);
        radius = Math.min(Math.abs(radius), 0.5 * (xMax - xMin), 0.5 * (yMax - yMin));
        if (radius === 0.0)
            return Loop_1.Loop.createPolygon([
                Point3dVector3d_1.Point3d.create(xMin, yMin, z),
                Point3dVector3d_1.Point3d.create(xMax, yMin, z),
                Point3dVector3d_1.Point3d.create(xMax, yMax, z),
                Point3dVector3d_1.Point3d.create(xMin, yMax, z),
                Point3dVector3d_1.Point3d.create(xMin, yMin, z),
            ]);
        else {
            const vectorU = Point3dVector3d_1.Vector3d.create(radius, 0, 0);
            const vectorV = Point3dVector3d_1.Vector3d.create(0, radius, 0);
            const x0A = xMin + radius;
            const y0A = yMin + radius;
            const x1A = xMax - radius;
            const y1A = yMax - radius;
            const centers = [
                Point3dVector3d_1.Point3d.create(x1A, y1A, z),
                Point3dVector3d_1.Point3d.create(x0A, y1A, z),
                Point3dVector3d_1.Point3d.create(x0A, y0A, z),
                Point3dVector3d_1.Point3d.create(x1A, y0A, z),
            ];
            const loop = Loop_1.Loop.create();
            for (let i = 0; i < 4; i++) {
                const center = centers[i];
                const nextCenter = centers[(i + 1) % 4];
                const edgeVector = Point3dVector3d_1.Vector3d.createStartEnd(center, nextCenter);
                const arc = Arc3d_1.Arc3d.create(center, vectorU, vectorV, AngleSweep_1.AngleSweep.createStartEndDegrees(0, 90));
                loop.tryAddChild(arc);
                const arcEnd = arc.endPoint();
                if (!edgeVector.isAlmostZero)
                    loop.tryAddChild(LineSegment3d_1.LineSegment3d.create(arcEnd, arcEnd.plus(edgeVector)));
                vectorU.rotate90CCWXY(vectorU);
                vectorV.rotate90CCWXY(vectorV);
            }
            return loop;
        }
    }
    /**
     * If `arcB` is a continuation of `arcA`, extend `arcA` (in place) to include the range of `arcB`
     * * This only succeeds if the two arcs are part of identical complete arcs and end of `arcA` matches the beginning of `arcB`.
     * @param arcA first arc, modified in place.
     * @param arcB second arc, unmodified.
     * @param allowReversed whether to consolidate even when second arc is reversed (default is `false`).
     * @param tolerance optional coordinate tolerance for point equality (default is `Geometry.smallMetricDistance`).
     * @returns whether `arcA` was modified.
     */
    static appendToArcInPlace(arcA, arcB, allowReverse = false, tolerance = Geometry_1.Geometry.smallMetricDistance) {
        if (arcA.center.isAlmostEqual(arcB.center, tolerance)) {
            const sweepSign = Geometry_1.Geometry.split3WaySign(arcA.sweep.sweepRadians * arcB.sweep.sweepRadians, -1, 0, 1);
            // evaluate derivatives wrt radians (not fraction!), but adjust direction for sweep signs
            const endA = arcA.angleToPointAndDerivative(arcA.sweep.fractionToAngle(1.0));
            if (arcA.sweep.sweepRadians < 0)
                endA.direction.scaleInPlace(-1.0);
            const startB = arcB.angleToPointAndDerivative(arcB.sweep.fractionToAngle(0.0));
            if (arcB.sweep.sweepRadians < 0)
                startB.direction.scaleInPlace(-1.0);
            if (endA.isAlmostEqual(startB, tolerance)) {
                arcA.sweep.setStartEndRadians(arcA.sweep.startRadians, arcA.sweep.startRadians + arcA.sweep.sweepRadians + sweepSign * arcB.sweep.sweepRadians);
                return true;
            }
            // Also ok if negated tangent
            if (allowReverse) {
                startB.direction.scaleInPlace(-1.0);
                if (endA.isAlmostEqual(startB, tolerance)) {
                    arcA.sweep.setStartEndRadians(arcA.sweep.startRadians, arcA.sweep.startRadians + arcA.sweep.sweepRadians - sweepSign * arcB.sweep.sweepRadians);
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Return a `Path` containing arcs are on the surface of an ellipsoid and pass through a sequence of points.
     * * Each arc passes through the two given endpoints and in the plane containing the true surface normal at
     * given `fractionForIntermediateNormal`
     * @param ellipsoid
     * @param pathPoints
     * @param fractionForIntermediateNormal fractional position for surface normal used to create the section plane.
     */
    static assembleArcChainOnEllipsoid(ellipsoid, pathPoints, fractionForIntermediateNormal = 0.5) {
        const arcPath = Path_1.Path.create();
        for (let i = 0; i + 1 < pathPoints.length; i++) {
            const arc = ellipsoid.sectionArcWithIntermediateNormal(pathPoints[i].toAngles(), fractionForIntermediateNormal, pathPoints[i + 1].toAngles());
            arcPath.tryAddChild(arc);
        }
        return arcPath;
    }
    static appendGeometryQueryArray(candidate, result) {
        if (candidate instanceof GeometryQuery_1.GeometryQuery)
            result.push(candidate);
        else if (Array.isArray(candidate)) {
            for (const p of candidate)
                this.appendGeometryQueryArray(p, result);
        }
    }
    /**
     * Create solid primitives for pipe segments (e.g. Cone or TorusPipe) around line and arc primitives.
     * @param centerline centerline geometry.
     * @param pipeRadius radius of pipe.
     */
    static createPipeSegments(centerline, pipeRadius) {
        if (centerline instanceof LineSegment3d_1.LineSegment3d) {
            return Cone_1.Cone.createAxisPoints(centerline.startPoint(), centerline.endPoint(), pipeRadius, pipeRadius, false);
        }
        else if (centerline instanceof Arc3d_1.Arc3d) {
            return TorusPipe_1.TorusPipe.createAlongArc(centerline, pipeRadius, false);
        }
        else if (centerline instanceof CurvePrimitive_1.CurvePrimitive) {
            const builder = PolyfaceBuilder_1.PolyfaceBuilder.create();
            builder.addMiteredPipes(centerline, pipeRadius);
            return builder.claimPolyface();
        }
        else if (centerline instanceof CurveCollection_1.CurveChain) {
            const result = [];
            for (const p of centerline.children) {
                const pipe = this.createPipeSegments(p, pipeRadius);
                this.appendGeometryQueryArray(pipe, result);
            }
            return result;
        }
        return undefined;
    }
    /** Get start point and tangent for variant curve data. */
    static startPointAndTangent(curve) {
        if (curve instanceof CurvePrimitive_1.CurvePrimitive)
            return curve.fractionToPointAndDerivative(0.0);
        if (curve.length < 2)
            return undefined;
        if (Array.isArray(curve))
            curve = new Point3dArrayCarrier_1.Point3dArrayCarrier(curve);
        const ray = Ray3d_1.Ray3d.createZero();
        curve.getPoint3dAtUncheckedPointIndex(0, ray.origin);
        curve.vectorIndexIndex(0, 1, ray.direction);
        return ray;
    }
    ;
    /** Create an [[Arc3d]] from `sectionData` that has its center at the start point of the centerline. */
    static createArcFromSectionData(centerline, sectionData) {
        const ray = CurveFactory.startPointAndTangent(centerline);
        if (!ray)
            return undefined;
        let arc;
        if (sectionData instanceof Arc3d_1.Arc3d) {
            arc = sectionData.clone();
            arc.center = ray.origin;
        }
        else {
            const vector0 = Point3dVector3d_1.Vector3d.create();
            const vector90 = Point3dVector3d_1.Vector3d.create();
            const length0 = (typeof sectionData === "number") ? sectionData : sectionData.x;
            const length90 = (typeof sectionData === "number") ? sectionData : sectionData.y;
            const baseFrame = Matrix3d_1.Matrix3d.createRigidHeadsUp(ray.direction, Geometry_1.AxisOrder.ZXY);
            baseFrame.columnX(vector0).scaleInPlace(length0);
            baseFrame.columnY(vector90).scaleInPlace(length90);
            arc = Arc3d_1.Arc3d.create(ray.origin, vector0, vector90);
        }
        if (arc.binormalVector().dotProduct(ray.direction) < 0.0)
            arc.reverseInPlace();
        return arc;
    }
    /**
     * Create section arcs for mitered pipe.
     * * At the end of each pipe segment, the pipe is mitered by the plane that bisects the angle between successive
     * centerline segments.
     * * The section arcs are constructed so that lines between corresponding fractional positions on the arcs are
     *   axial lines on the pipes.
     * * This means that the initial arc's vector0 and vector90 lengths and angular separation are _not_ preserved in
     * the section arcs.
     * * Circular or elliptical pipe cross sections can be specified by supplying either a radius, a pair of semi-axis
     * lengths, or an Arc3d:
     *    * For semi-axis length input, x and y correspond to ellipse local axes perpendicular to each other and to the
     * start tangent.
     *    * For Arc3d input, the center is translated to the centerline start point. For best results, ensure this arc
     * is perpendicular to the centerline start tangent.
     * * This function internally calls [[createMiteredSweepSections]] with default options.
     * @param centerline centerline of pipe. For best results, ensure no successive duplicate points with e.g.,
     * [[GrowableXYZArray.createCompressed]].
     * @param sectionData circle radius, ellipse semi-axis lengths, or full Arc3d (if not full, function makes it full).
     * @returns array of sections or empty array if section creation failed.
     */
    static createMiteredPipeSections(centerline, sectionData) {
        const arc = CurveFactory.createArcFromSectionData(centerline, sectionData);
        if (!arc)
            return [];
        const miteredSweeps = CurveFactory.createMiteredSweepSections(centerline, arc);
        if (miteredSweeps)
            return miteredSweeps.sections;
        return [];
    }
    /** For a smooth curve, stroke and return unnormalized start/end tangents. */
    static strokeSmoothCurve(curve, options) {
        let startTangent, endTangent;
        if (curve instanceof CurvePrimitive_1.CurvePrimitive) {
            startTangent = curve.fractionToPointAndDerivative(0.0).direction;
            endTangent = curve.fractionToPointAndDerivative(1.0).direction;
            const strokes = LineString3d_1.LineString3d.create();
            curve.emitStrokes(strokes, options);
            curve = strokes.packedPoints;
        }
        else if (curve instanceof CurveCollection_1.CurveChain) {
            startTangent = curve.startPointAndDerivative()?.direction;
            endTangent = curve.endPointAndDerivative()?.direction;
            const strokes = curve.getPackedStrokes(options);
            if (!strokes)
                return undefined;
            curve = strokes;
        }
        else if (Array.isArray(curve))
            curve = new Point3dArrayCarrier_1.Point3dArrayCarrier(curve);
        return { strokes: curve, startTangent, endTangent };
    }
    /**
     * Align two bisector plane normals to given smooth rail tangents or optional overrides.
     * * Optionally average the normals for physically closed rail.
     */
    static alignFirstAndLastBisectorPlanes(firstPlane, lastPlane, smoothRailData, options) {
        const normal0 = options?.startTangent ?? (smoothRailData?.startTangent ?? firstPlane.getNormalRef());
        const normal1 = options?.endTangent ?? (smoothRailData?.endTangent ?? lastPlane.getNormalRef());
        if (options?.wrapIfPhysicallyClosed && firstPlane.getOriginRef().isAlmostEqual(lastPlane.getOriginRef())) {
            const avgNormal = normal0.plus(normal1);
            if (avgNormal.tryNormalizeInPlace()) { // ignore cusp at seam
                firstPlane.getNormalRef().setFrom(avgNormal);
                lastPlane.getNormalRef().setFrom(avgNormal);
                return;
            }
        }
        if (normal0.tryNormalizeInPlace())
            firstPlane.getNormalRef().setFrom(normal0);
        if (normal1.tryNormalizeInPlace())
            lastPlane.getNormalRef().setFrom(normal1);
    }
    /** Reverse a closed curve or region if necessary so that its orientation is CCW with respect to the plane normal. */
    static alignClosedCurveToPlane(curve, planeNormal) {
        let closedCurve;
        if (curve instanceof CurvePrimitive_1.CurvePrimitive) {
            if (curve.startPoint().isAlmostEqual(curve.endPoint()))
                closedCurve = Loop_1.Loop.create(curve);
        }
        else if (curve.isAnyRegion())
            closedCurve = curve;
        if (closedCurve) {
            // The alignment condition is equivalent to positive projected curve area computed wrt to the plane normal.
            const toLocal = Matrix3d_1.Matrix3d.createRigidHeadsUp(planeNormal).transpose();
            const projection = closedCurve.cloneTransformed(Transform_1.Transform.createOriginAndMatrix(undefined, toLocal));
            if (projection) { // now we can ignore z-coords
                const areaXY = RegionOps_1.RegionOps.computeXYArea(projection);
                if (areaXY && areaXY < 0)
                    curve.reverseInPlace();
            }
        }
    }
    /**
     * Projection to target plane, constructing sweep direction from two given planes.
     * * If successful, push the target plane and swept section to the output arrays and return the swept section.
     * * If unsuccessful, leave the output arrays alone and return the input section.
     */
    static doSweepToPlane(output, edgePlane0, edgePlane1, targetPlane, section) {
        const sweepVector = Point3dVector3d_1.Vector3d.createStartEnd(edgePlane0.getOriginRef(), edgePlane1.getOriginRef());
        const transform = Transform_1.Transform.createFlattenAlongVectorToPlane(sweepVector, targetPlane.getOriginRef(), targetPlane.getNormalRef());
        if (transform === undefined)
            return section;
        const transformedSection = section.cloneTransformed(transform);
        if (transformedSection === undefined)
            return section;
        output.planes.push(targetPlane);
        output.sections.push(transformedSection);
        return transformedSection;
    }
    /**
     * Sweep the `initialSection` along each segment of the (stroked) `centerline` until it hits the bisector plane at
     * the next vertex.
     * * For best results, the caller should place `initialSection` in a plane perpendicular to the `centerline`
     * start tangent.
     *   * This plane is commonly (but not necessarily) through the centerline start point itself.
     *   * To compute the sections, `initialSection` is projected in the direction of the centerline start tangent onto
     * the first bisector plane at the centerline start. The result of this projection will be likewise projected onto
     * the second plane, and so on in sequence.
     * * By default, the first/last bisector plane normals are set to the centerline start/end tangents. The caller can
     * override these with tangents supplied in `options`. If the centerline is physically closed and
     * `options.wrapIfPhysicallyClosed` is true, the first and last plane normals are averaged and equated.
     * * The centerline path does NOT have to be planar, however non-planarity will result in twisting of the sections
     * in the bisector planes.
     * @param centerline sweep path. Will be stroked if smooth.
     * @param initialSection profile curve to be swept. As noted above, this should be on a plane perpendicular to the
     * centerline start tangent.
     * @param options options for computation and output.
     * @return array of sections, formed from projecting `initialSection` successively onto the bisector planes.
     */
    static createMiteredSweepSections(centerline, initialSection, options) {
        if (!options)
            options = {};
        const rail = this.strokeSmoothCurve(centerline, options.strokeOptions);
        if (!rail)
            return undefined;
        const planes = PolylineOps_1.PolylineOps.createBisectorPlanesForDistinctPoints(rail.strokes);
        if (!planes || planes.length < 2)
            return undefined;
        this.alignFirstAndLastBisectorPlanes(planes[0], planes[planes.length - 1], rail, options);
        // RuledSweep facet construction assumes the contours are oriented CCW with respect to the sweep direction so that
        // facet normals point outward. We only have to align the first contour; the rest will inherit its orientation.
        this.alignClosedCurveToPlane(initialSection, planes[0].getNormalRef());
        const sectionData = { sections: [], planes: [] };
        let currentSection = this.doSweepToPlane(sectionData, planes[0], planes[1], planes[0], initialSection);
        for (let i = 1; i < planes.length; i++)
            currentSection = this.doSweepToPlane(sectionData, planes[i - 1], planes[i], planes[i], currentSection);
        if (options.outputSelect) {
            const ruledSweep = RuledSweep_1.RuledSweep.create(sectionData.sections, options.capped ?? false);
            if (ruledSweep) {
                sectionData.ruledSweep = ruledSweep;
                if (MiteredSweepOutputSelect.AlsoMesh === options.outputSelect) {
                    const builder = PolyfaceBuilder_1.PolyfaceBuilder.create(options.strokeOptions);
                    builder.addRuledSweep(ruledSweep);
                    sectionData.mesh = builder.claimPolyface();
                }
            }
        }
        return sectionData;
    }
    /**
     * Create a circular arc from start point, tangent at start, radius, optional plane normal, arc sweep.
     * * The vector from start point to center is in the direction of upVector crossed with tangentA.
     * @param start start point.
     * @param tangentAtStart vector in tangent direction at the start.
     * @param radius signed radius.
     * @param upVector optional out-of-plane vector. Defaults to positive Z.
     * @param sweep angular range. If single `Angle` is given, start angle is at 0 degrees (the start point).
     */
    static createArcPointTangentRadius(start, tangentAtStart, radius, upVector, sweep) {
        return Arc3d_1.Arc3d.createCircularStartTangentRadius(start, tangentAtStart, radius, upVector, sweep);
    }
    /**
     * Compute 2 spirals (all in XY) for a symmetric line-to-line transition.
     * * First spiral begins at given start point.
     * * first tangent aims at shoulder.
     * * outbound spiral joins line from shoulder to target.
     * @param spiralType name of spiral type. THIS MUST BE AN "Integrated" SPIRAL TYPE.
     * @param startPoint inbound start point.
     * @param shoulder point target point for (both) spiral-to-line tangencies.
     * @return array with the computed spirals, or undefined if failure.
     */
    static createLineSpiralSpiralLine(spiralType, startPoint, shoulderPoint, targetPoint) {
        const vectorAB = Point3dVector3d_1.Vector3d.createStartEnd(startPoint, shoulderPoint);
        const vectorBC0 = Point3dVector3d_1.Vector3d.createStartEnd(shoulderPoint, targetPoint);
        const referenceLength = vectorAB.magnitude();
        const radiansAB = Math.atan2(vectorAB.y, vectorAB.x);
        const lineTurnRadians = vectorAB.angleToXY(vectorBC0);
        const spiralTurnRadians = 0.5 * lineTurnRadians.radians;
        const radiansBC = radiansAB + lineTurnRadians.radians;
        const axesA = Matrix3d_1.Matrix3d.createRotationAroundAxisIndex(Geometry_1.AxisIndex.Z, Angle_1.Angle.createRadians(radiansAB));
        const frameA = Transform_1.Transform.createRefs(startPoint.clone(), axesA);
        // We know how much it has to turn, and but not the length or end radius.
        // make a spiral of referenceLength and scale it back to the junction line
        const spiralARefLength = IntegratedSpiral3d_1.IntegratedSpiral3d.createFrom4OutOf5(spiralType, 0.0, undefined, Angle_1.Angle.createRadians(0), Angle_1.Angle.createRadians(spiralTurnRadians), referenceLength, undefined, frameA);
        if (spiralARefLength) {
            const midPlanePerpendicularRadians = radiansAB + spiralTurnRadians;
            const midPlanePerpendicularVector = Point3dVector3d_1.Vector3d.createPolar(1.0, Angle_1.Angle.createRadians(midPlanePerpendicularRadians));
            const altitudeB = midPlanePerpendicularVector.dotProductStartEnd(startPoint, shoulderPoint);
            const altitudeSpiralEnd = midPlanePerpendicularVector.dotProductStartEnd(startPoint, spiralARefLength.endPoint());
            const scaleFactor = altitudeB / altitudeSpiralEnd;
            const spiralA = IntegratedSpiral3d_1.IntegratedSpiral3d.createFrom4OutOf5(spiralType, 0.0, undefined, Angle_1.Angle.createRadians(0), Angle_1.Angle.createRadians(spiralTurnRadians), referenceLength * scaleFactor, undefined, frameA);
            const distanceAB = vectorAB.magnitude();
            const vectorBC = Point3dVector3d_1.Vector3d.createStartEnd(shoulderPoint, targetPoint);
            vectorBC.scaleToLength(distanceAB, vectorBC);
            const pointC = shoulderPoint.plus(vectorBC);
            const axesC = Matrix3d_1.Matrix3d.createRotationAroundAxisIndex(Geometry_1.AxisIndex.Z, Angle_1.Angle.createRadians(radiansBC + Math.PI));
            const frameC = Transform_1.Transform.createRefs(pointC, axesC);
            const spiralC = IntegratedSpiral3d_1.IntegratedSpiral3d.createFrom4OutOf5(spiralType, 0, -spiralA.radius01.x1, Angle_1.Angle.zero(), undefined, spiralA.curveLength(), Segment1d_1.Segment1d.create(1, 0), frameC);
            return [spiralA, spiralC];
        }
        return undefined;
    }
    /**
     * Compute 2 spirals (all in XY) for a symmetric line-to-line transition.
     * * Spiral length is given.
     * * tangency points float on both lines.
     * @param spiralType name of spiral type.  THIS MUST BE AN "Integrated" SPIRAL TYPE.
     * @param pointA inbound start point.
     * @param shoulder point target point for (both) spiral-to-line tangencies.
     * @param spiralLength for each part of the spiral pair.
     * @return array with the computed spirals, or undefined if failure.
     */
    static createLineSpiralSpiralLineWithSpiralLength(spiralType, pointA, pointB, pointC, spiralLength) {
        const vectorAB = Point3dVector3d_1.Vector3d.createStartEnd(pointA, pointB);
        const vectorBC = Point3dVector3d_1.Vector3d.createStartEnd(pointB, pointC);
        const radiansAB = Math.atan2(vectorAB.y, vectorAB.x);
        const lineTurnAngle = vectorAB.angleToXY(vectorBC);
        const spiralTurnRadians = 0.5 * lineTurnAngle.radians;
        const bisectorRadians = 0.5 * (Math.PI - lineTurnAngle.radians);
        const radiansCB = Math.atan2(-vectorBC.y, -vectorBC.x);
        const spiralAB0 = IntegratedSpiral3d_1.IntegratedSpiral3d.createFrom4OutOf5(spiralType, 0, undefined, Angle_1.Angle.zero(), Angle_1.Angle.createRadians(spiralTurnRadians), spiralLength, undefined, Transform_1.Transform.createIdentity());
        if (spiralAB0) {
            const localEndPoint = spiralAB0.fractionToPoint(1);
            const distanceAB = pointA.distance(pointB);
            const distanceCB = pointC.distance(pointB);
            // The spiral eventually has to end on the bisector, at localEndPoint.y height from the inbound line
            // distance from shoulder to projection of that point to point E on the inbound line is
            const distanceBE = localEndPoint.y / Math.tan(bisectorRadians);
            const xFractionAB = Geometry_1.Geometry.conditionalDivideFraction(distanceAB - distanceBE - localEndPoint.x, distanceAB);
            const xFractionCB = Geometry_1.Geometry.conditionalDivideFraction(distanceCB - distanceBE - localEndPoint.x, distanceCB);
            if (xFractionAB !== undefined && xFractionCB !== undefined) {
                const axesA = Matrix3d_1.Matrix3d.createRotationAroundAxisIndex(Geometry_1.AxisIndex.Z, Angle_1.Angle.createRadians(radiansAB));
                const frameAOrigin = pointA.interpolate(xFractionAB, pointB);
                const frameA = Transform_1.Transform.createRefs(frameAOrigin, axesA);
                const spiralAB = IntegratedSpiral3d_1.IntegratedSpiral3d.createFrom4OutOf5(spiralType, 0, undefined, Angle_1.Angle.zero(), Angle_1.Angle.createRadians(spiralTurnRadians), spiralLength, undefined, frameA);
                const axesB = Matrix3d_1.Matrix3d.createRotationAroundAxisIndex(Geometry_1.AxisIndex.Z, Angle_1.Angle.createRadians(radiansCB));
                const frameBOrigin = pointC.interpolate(xFractionCB, pointB);
                const frameB = Transform_1.Transform.createRefs(frameBOrigin, axesB);
                const spiralBC = IntegratedSpiral3d_1.IntegratedSpiral3d.createFrom4OutOf5(spiralType, 0, undefined, Angle_1.Angle.zero(), Angle_1.Angle.createRadians(-spiralTurnRadians), spiralLength, undefined, frameB);
                return [spiralAB, spiralBC];
            }
        }
        return undefined;
    }
    /**
     * Compute 2 spirals and an arc (all in XY) for a symmetric line-to-line transition.
     * Spiral lengths and arc radius are given (e.g., from design speed standards).
     * @param spiralType name of spiral type. THIS MUST BE AN "Integrated" SPIRAL TYPE.
     * @param pointA inbound start point.
     * @param pointB shoulder (target)  point for (both) spiral-to-line tangencies.
     * @param lengthA inbound spiral length.
     * @param lengthB outbound spiral length.
     * @return array with the computed spirals, or undefined if failure.
     */
    static createLineSpiralArcSpiralLine(spiralType, pointA, pointB, pointC, lengthA, lengthB, arcRadius) {
        const vectorAB = Point3dVector3d_1.Vector3d.createStartEnd(pointA, pointB);
        vectorAB.z = 0;
        const vectorCB = Point3dVector3d_1.Vector3d.createStartEnd(pointC, pointB);
        vectorCB.z = 0;
        const unitAB = vectorAB.normalize();
        const unitCB = vectorCB.normalize();
        if (unitAB === undefined || unitCB === undefined)
            return undefined;
        const unitPerpAB = unitAB.unitPerpendicularXY();
        const unitPerpCB = unitCB.unitPerpendicularXY();
        const thetaABC = vectorAB.angleToXY(vectorCB);
        const sideA = Geometry_1.Geometry.split3WaySign(thetaABC.radians, 1, -1, -1);
        const sideB = -sideA;
        const radiusA = sideA * Math.abs(arcRadius);
        const radiusB = sideB * Math.abs(arcRadius);
        const spiralA = IntegratedSpiral3d_1.IntegratedSpiral3d.createFrom4OutOf5(spiralType, 0, radiusA, Angle_1.Angle.zero(), undefined, lengthA, undefined, Transform_1.Transform.createIdentity());
        const spiralB = IntegratedSpiral3d_1.IntegratedSpiral3d.createFrom4OutOf5(spiralType, 0, radiusB, Angle_1.Angle.zero(), undefined, lengthB, undefined, Transform_1.Transform.createIdentity());
        const spiralEndA = spiralA.fractionToPointAndUnitTangent(1.0);
        const spiralEndB = spiralB.fractionToPointAndUnitTangent(1.0);
        // From the end of spiral, step away to arc center (and this is in local coordinates of each spiral)
        const sA = spiralEndA.origin.x - radiusA * spiralEndA.direction.y;
        const tA = spiralEndA.origin.y + radiusA * spiralEndA.direction.x;
        const sB = spiralEndB.origin.x - radiusB * spiralEndB.direction.y;
        const tB = spiralEndB.origin.y + radiusB * spiralEndB.direction.x;
        // Those local coordinates are rotated to unitAB and unitBC ...
        const vectorA = Point3dVector3d_1.Vector3d.createAdd2Scaled(unitAB, sA, unitPerpAB, tA);
        const vectorB = Point3dVector3d_1.Vector3d.createAdd2Scaled(unitCB, sB, unitPerpCB, tB);
        const uv = Point2dVector2d_1.Vector2d.create();
        if (SmallSystem_1.SmallSystem.linearSystem2d(unitAB.x, -unitCB.x, unitAB.y, -unitCB.y, vectorB.x - vectorA.x, vectorB.y - vectorA.y, uv)) {
            const tangencyAB = pointB.plusScaled(unitAB, uv.x);
            const tangencyCB = pointB.plusScaled(unitCB, uv.y);
            const frameA = Transform_1.Transform.createOriginAndMatrixColumns(tangencyAB, unitAB, unitPerpAB, Point3dVector3d_1.Vector3d.unitZ());
            const frameB = Transform_1.Transform.createOriginAndMatrixColumns(tangencyCB, unitCB, unitPerpCB, Point3dVector3d_1.Vector3d.unitZ());
            spiralA.tryTransformInPlace(frameA);
            spiralB.tryTransformInPlace(frameB);
            const rayA1 = spiralA.fractionToPointAndUnitTangent(1.0);
            const rayB0 = spiralB.fractionToPointAndUnitTangent(1.0);
            rayB0.direction.scaleInPlace(-1.0);
            const sweep = rayA1.direction.angleToXY(rayB0.direction);
            if (radiusA < 0)
                sweep.setRadians(-sweep.radians);
            const arc = CurveFactory.createArcPointTangentRadius(rayA1.origin, rayA1.direction, radiusA, undefined, sweep);
            return [spiralA, arc, spiralB];
        }
        return undefined;
    }
    /** Return the intersection point of 3 planes. */
    static planePlaneIntersectionRay(planeA, planeB) {
        const altitudeA = planeA.altitudeXYZ(0, 0, 0);
        const altitudeB = planeB.altitudeXYZ(0, 0, 0);
        const normalAx = planeA.normalX();
        const normalAy = planeA.normalY();
        const normalAz = planeA.normalZ();
        const normalBx = planeB.normalX();
        const normalBy = planeB.normalY();
        const normalBz = planeB.normalZ();
        const normalCx = Geometry_1.Geometry.crossProductXYXY(normalAy, normalAz, normalBy, normalBz);
        const normalCy = Geometry_1.Geometry.crossProductXYXY(normalAz, normalAx, normalBz, normalBx);
        const normalCz = Geometry_1.Geometry.crossProductXYXY(normalAx, normalAy, normalBx, normalBy);
        const rayOrigin = SmallSystem_1.SmallSystem.linearSystem3d(normalAx, normalAy, normalAz, normalBx, normalBy, normalBz, normalCx, normalCy, normalCz, -altitudeA, -altitudeB, 0.0);
        if (rayOrigin !== undefined) {
            return Ray3d_1.Ray3d.createXYZUVW(rayOrigin.x, rayOrigin.y, rayOrigin.z, normalCx, normalCy, normalCz);
        }
        return undefined;
    }
}
exports.CurveFactory = CurveFactory;
//# sourceMappingURL=CurveFactory.js.map