"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.PlaneAltitudeRangeContext = void 0;
const Geometry_1 = require("../../Geometry");
const Angle_1 = require("../../geometry3d/Angle");
const GeometryHandler_1 = require("../../geometry3d/GeometryHandler");
const GrowableXYZArray_1 = require("../../geometry3d/GrowableXYZArray");
const Plane3dByOriginAndUnitNormal_1 = require("../../geometry3d/Plane3dByOriginAndUnitNormal");
const Point3dVector3d_1 = require("../../geometry3d/Point3dVector3d");
const Range_1 = require("../../geometry3d/Range");
const Ray3d_1 = require("../../geometry3d/Ray3d");
const GeometryQuery_1 = require("../GeometryQuery");
const LineSegment3d_1 = require("../LineSegment3d");
const LineString3d_1 = require("../LineString3d");
const StrokeOptions_1 = require("../StrokeOptions");
/**
 * Accumulator context for searching for extrema of geometry along a plane.
 * @internal
 */
class PlaneAltitudeRangeContext extends GeometryHandler_1.RecurseToCurvesGeometryHandler {
    plane;
    range;
    lowPoint;
    highPoint;
    constructor(plane) {
        super();
        this.plane = plane;
        this.range = Range_1.Range1d.createNull();
        this.resetRange();
    }
    resetRange() {
        this.range.setNull();
    }
    announcePoint(point) {
        const h = this.plane.altitude(point);
        if (this.range.extendLow(h))
            this.lowPoint = point.clone(this.lowPoint);
        if (this.range.extendHigh(h))
            this.highPoint = point.clone(this.highPoint);
    }
    announcePoints(points) {
        for (let i = 0; i < points.length; i++) {
            const h = points.evaluateUncheckedIndexPlaneAltitude(i, this.plane);
            if (this.range.extendLow(h))
                this.lowPoint = points.getPoint3dAtUncheckedPointIndex(i, this.lowPoint);
            if (this.range.extendHigh(h))
                this.highPoint = points.getPoint3dAtUncheckedPointIndex(i, this.highPoint);
        }
    }
    static createCapture(plane) {
        const context = new PlaneAltitudeRangeContext(plane);
        return context;
    }
    handleLineSegment3d(segment) {
        this.announcePoint(segment.point0Ref);
        this.announcePoint(segment.point1Ref);
    }
    handleLineString3d(lineString) {
        this.announcePoints(lineString.packedPoints);
    }
    _strokeOptions;
    initStrokeOptions() {
        // TODO: compute the exact extrema; until then stroke aggressively
        if (undefined === this._strokeOptions) {
            this._strokeOptions = new StrokeOptions_1.StrokeOptions();
            this._strokeOptions.angleTol = Angle_1.Angle.createDegrees(1);
        }
    }
    handleBSplineCurve3d(bcurve) {
        // ugh.   The point MUST be on the curve -- usual excess-range of poles is not ok.
        this.initStrokeOptions();
        const ls = LineString3d_1.LineString3d.create();
        bcurve.emitStrokes(ls, this._strokeOptions);
        this.handleLineString3d(ls);
    }
    handleBSplineCurve3dH(bcurve) {
        // ugh.   The point MUST be on the curve -- usual excess-range of poles is not ok.
        this.initStrokeOptions();
        const ls = LineString3d_1.LineString3d.create();
        bcurve.emitStrokes(ls, this._strokeOptions);
        this.handleLineString3d(ls);
    }
    _sineCosinePolynomial;
    _workPoint;
    handleArc3d(g) {
        this._sineCosinePolynomial = g.getPlaneAltitudeSineCosinePolynomial(this.plane, this._sineCosinePolynomial);
        let radians = this._sineCosinePolynomial.referenceMinMaxRadians();
        if (g.sweep.isRadiansInSweep(radians))
            this.announcePoint((this._workPoint = g.radiansToPoint(radians, this._workPoint)));
        radians += Math.PI;
        if (g.sweep.isRadiansInSweep(radians))
            this.announcePoint((this._workPoint = g.radiansToPoint(radians, this._workPoint)));
        this.announcePoint((this._workPoint = g.startPoint(this._workPoint)));
        this.announcePoint((this._workPoint = g.endPoint(this._workPoint)));
    }
    static findExtremesInDirection(geometry, direction) {
        const origin = direction instanceof Ray3d_1.Ray3d ? direction.origin : Point3dVector3d_1.Point3d.createZero();
        const vector = direction instanceof Ray3d_1.Ray3d ? direction.direction : direction;
        const plane = Plane3dByOriginAndUnitNormal_1.Plane3dByOriginAndUnitNormal.create(origin, vector); // vector is normalized, so altitudes are distances
        if (plane) {
            const context = new PlaneAltitudeRangeContext(plane);
            if (geometry instanceof GeometryQuery_1.GeometryQuery) {
                geometry.dispatchToGeometryHandler(context);
            }
            else if (geometry instanceof GrowableXYZArray_1.GrowableXYZArray) {
                context.announcePoints(geometry);
            }
            else {
                for (const pt of geometry)
                    context.announcePoint(pt);
            }
            return context;
        }
        return undefined;
    }
    /**
     * Compute altitudes for the geometry (via dispatch) over the plane defined by the given direction, and
     * return points at min and max altitude, packed into a `LineSegment3d`.
     * @param geometry geometry to project
     * @param direction vector or ray on which to project the instance. A `Vector3d` is treated as a `Ray3d` with
     * zero origin.
     * @param lowHigh optional receiver for output
    */
    static findExtremePointsInDirection(geometry, direction, lowHigh) {
        const context = this.findExtremesInDirection(geometry, direction);
        if (context && context.highPoint && context.lowPoint)
            return LineSegment3d_1.LineSegment3d.create(context.lowPoint, context.highPoint, lowHigh);
        return undefined;
    }
    /**
     * Compute altitudes for the geometry (via dispatch) over the plane defined by the given direction, and return
     * the min and max altitudes, packed into a Range1d.
     * @param geometry geometry to project
     * @param direction vector or ray on which to project the instance. A `Vector3d` is treated as a `Ray3d` with
     * zero origin.
     * @param lowHigh optional receiver for output
    */
    static findExtremeAltitudesInDirection(geometry, direction, lowHigh) {
        const context = this.findExtremesInDirection(geometry, direction);
        if (context && !context.range.isNull)
            return Range_1.Range1d.createFrom(context.range, lowHigh);
        return undefined;
    }
    /**
     * Project geometry (via dispatch) onto the given ray, and return the extreme fractional parameters of projection.
     * @param geometry geometry to project
     * @param direction vector or ray onto which the instance is projected. A `Vector3d` is treated as a `Ray3d` with
     * zero origin.
     * @param lowHigh optional receiver for output
     */
    static findExtremeFractionsAlongDirection(geometry, direction, lowHigh) {
        const range = this.findExtremeAltitudesInDirection(geometry, direction, lowHigh);
        if (undefined !== range) {
            const mag = (direction instanceof Point3dVector3d_1.Vector3d) ? direction.magnitude() : direction.direction.magnitude();
            const scaleToFraction = Geometry_1.Geometry.conditionalDivideCoordinate(1.0, mag);
            if (undefined !== scaleToFraction) {
                range.low *= scaleToFraction;
                range.high *= scaleToFraction;
                return range;
            }
        }
        return undefined;
    }
}
exports.PlaneAltitudeRangeContext = PlaneAltitudeRangeContext;
//# sourceMappingURL=PlaneAltitudeRangeContext.js.map