"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module RangeSearch
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwoTreeSearchHandlerForFacetFacetCloseApproach = exports.SingleTreeSearchHandlerForClosestPointOnPolyface = exports.TwoTreeSearchHandlerForLineString3dLineString3dCloseApproach = exports.SingleTreeSearchHandlerForClosestPointOnLineString3d = exports.TwoTreeSearchHandlerForPoint3dArrayPoint3dArrayCloseApproach = exports.SingleTreeSearchHandlerForClosestPointInArray = void 0;
const CurveLocationDetail_1 = require("../../curve/CurveLocationDetail");
const LineSegment3d_1 = require("../../curve/LineSegment3d");
const LineString3d_1 = require("../../curve/LineString3d");
const Geometry_1 = require("../../Geometry");
const Point3dVector3d_1 = require("../../geometry3d/Point3dVector3d");
const PolygonOps_1 = require("../../geometry3d/PolygonOps");
const FacetLocationDetail_1 = require("../FacetLocationDetail");
const MinimumValueTester_1 = require("./MinimumValueTester");
const RangeTreeNode_1 = require("./RangeTreeNode");
/**
 * Helper class for searching for the closest point in a set of points.
 * * Future optimization: avoid sqrt by using squared distance throughout (would require refactoring CurveLocationDetail).
 * @internal
 */
class SingleTreeSearchHandlerForClosestPointInArray extends RangeTreeNode_1.SingleTreeSearchHandler {
    /** The calling context */
    context;
    /** Evolving search state */
    searchState;
    /** Space point for the search  */
    spacePoint;
    /**
     * Constructor
     * @param spacePoint cloned
     * @param context captured
     * @param maxDist collect points at no more than this distance from spacePoint
     */
    constructor(spacePoint, context, maxDist) {
        super();
        this.context = context;
        if (maxDist !== undefined && maxDist < 0)
            maxDist = undefined;
        this.searchState = MinimumValueTester_1.MinimumValueTester.create(maxDist);
        this.spacePoint = Point3dVector3d_1.Point3d.createFrom(spacePoint);
    }
    /** Return the current closest point */
    getResult() {
        if (this.searchState.minValue !== undefined && this.searchState.itemAtMinValue !== undefined) {
            const iPoint = this.searchState.itemAtMinValue;
            const cld = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(undefined, iPoint, this.context.points[iPoint]);
            cld.a = this.searchState.minValue;
            return cld;
        }
        return undefined;
    }
    /** Return the collected closest points (if collecting) */
    getSavedItems() {
        if (this.searchState.savedItems.length === 0)
            return undefined;
        const cldArray = [];
        for (let i = 0; i < this.searchState.savedItems.length; ++i) {
            const iPoint = this.searchState.savedItems[i];
            const cld = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(undefined, iPoint, this.context.points[iPoint]);
            cld.a = this.searchState.savedValues[i];
            cldArray.push(cld);
        }
        return cldArray;
    }
    /**
     * Return true if appData within the range should be offered to `processAppData`.
     * @param range range containing items to be tested.
     * @returns true if the spacePoint is within the range or close enough that a point in the range could be the closest.
     */
    isRangeActive(range) {
        const dMin = this.context.xyOnly ? range.distanceToPointXY(this.spacePoint) : range.distanceToPoint(this.spacePoint);
        if (this.searchState.isNewMinValue(dMin)) {
            this.context.numRangeTestTrue++;
            return true;
        }
        this.context.numRangeTestFalse++;
        return false;
    }
    /** Test a point indexed in the range tree as candidate for "closest" */
    processAppData(candidateIndex) {
        const d = this.context.xyOnly ? this.spacePoint.distanceXY(this.context.points[candidateIndex]) : this.spacePoint.distance(this.context.points[candidateIndex]);
        this.context.numPointTest++;
        this.searchState.testAndSave(candidateIndex, d);
    }
}
exports.SingleTreeSearchHandlerForClosestPointInArray = SingleTreeSearchHandlerForClosestPointInArray;
/**
 * Helper class for searching for the closest approach between sets of points.
 * * Future optimization: avoid sqrt by using squared distance throughout (would require refactoring CurveLocationDetail).
 * @internal
 */
class TwoTreeSearchHandlerForPoint3dArrayPoint3dArrayCloseApproach extends RangeTreeNode_1.TwoTreeDistanceMinimizationSearchHandler {
    /** Context for first set of points */
    contextA;
    /** Context for second set of points */
    contextB;
    /** Search state with current min distance point pair */
    searchState;
    /** True if and only if both contexts specify xy-only. */
    _xyOnly;
    /**
     * Constructor
     * @param contextA captured
     * @param contextB captured
     * @param maxDist collect points at no more than this separation distance
     */
    constructor(contextA, contextB, maxDist) {
        super();
        this.contextA = contextA;
        this.contextB = contextB;
        if (maxDist !== undefined && maxDist < 0)
            maxDist = undefined;
        this.searchState = MinimumValueTester_1.MinimumValueTester.create(maxDist);
        this._xyOnly = contextA.xyOnly && contextB.xyOnly;
    }
    /** Return the current closest approach */
    getResult() {
        if (this.searchState.minValue !== undefined) {
            return this.searchState.itemAtMinValue;
        }
        return undefined;
    }
    /** Return the collected close approaches (if collecting) */
    getSavedItems() {
        if (this.searchState.savedItems.length > 0) {
            return this.searchState.savedItems;
        }
        return undefined;
    }
    /** Get current min distance */
    getCurrentDistance() {
        const d = this.searchState.minValue;
        return d === undefined ? Number.MAX_VALUE : d;
    }
    /** Whether to ignore z-coordinates in distance computations. */
    getXYOnly() { return this._xyOnly ?? false; }
    /** Compute and test the distance between two points, given their indices. */
    processAppDataPair(indexA, indexB) {
        this.contextA.numPointTest++;
        const pointA = this.contextA.points[indexA];
        const pointB = this.contextB.points[indexB];
        const d = this._xyOnly ? Geometry_1.Geometry.distanceXYXY(pointA.x, pointA.y, pointB.x, pointB.y) : Geometry_1.Geometry.distanceXYZXYZ(pointA.x, pointA.y, pointA.z, pointB.x, pointB.y, pointB.z);
        if (this.searchState.isNewMinOrTrigger(d)) {
            const cldPair = CurveLocationDetail_1.CurveLocationDetailPair.createCapture(CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(undefined, indexA, pointA), CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(undefined, indexB, pointB));
            cldPair.detailA.a = cldPair.detailB.a = d;
            this.searchState.testAndSave(cldPair, d);
        }
    }
}
exports.TwoTreeSearchHandlerForPoint3dArrayPoint3dArrayCloseApproach = TwoTreeSearchHandlerForPoint3dArrayPoint3dArrayCloseApproach;
/**
 * Helper class for searching for the closest point in a linestring.
 * * Future optimization: avoid sqrt by using squared distance throughout (would require refactoring CurveLocationDetail).
 * @internal
 */
class SingleTreeSearchHandlerForClosestPointOnLineString3d extends RangeTreeNode_1.SingleTreeSearchHandler {
    /** The calling context */
    context;
    /** Evolving search state */
    searchState;
    /** Space point for the search */
    spacePoint;
    /**
     * Constructor
     * @param spacePoint cloned
     * @param context captured
     * @param maxDist collect points at no more than this distance from spacePoint
     */
    constructor(spacePoint, context, maxDist) {
        super();
        this.context = context;
        if (maxDist !== undefined && maxDist < 0)
            maxDist = undefined;
        this.searchState = MinimumValueTester_1.MinimumValueTester.create(maxDist);
        this.spacePoint = spacePoint.clone();
    }
    /** Return the current closest point */
    getResult() {
        if (this.searchState.minValue !== undefined && this.searchState.itemAtMinValue !== undefined) {
            return this.searchState.itemAtMinValue;
        }
        return undefined;
    }
    /** Return the collected closest points (if collecting) */
    getSavedItems() {
        if (this.searchState.savedItems.length > 0) {
            return this.searchState.savedItems;
        }
        return undefined;
    }
    /**
     * Return true if appData within the range should be offered to `processAppData`.
     * @param range range containing items to be tested.
     * @returns true if the spacePoint is within the range or close enough that a point in the range could be the closest.
     */
    isRangeActive(range) {
        const dMin = range.distanceToPoint(this.spacePoint);
        if (this.searchState.isNewMinValue(dMin)) {
            this.context.numRangeTestTrue++;
            return true;
        }
        this.context.numRangeTestFalse++;
        return false;
    }
    _workSegment;
    /** Test a segment indexed in the range tree as candidate for "closest" */
    processAppData(candidateIndex) {
        const segment = this._workSegment = this.context.lineString.getIndexedSegment(candidateIndex, this._workSegment);
        if (segment) {
            const cld = segment.closestPoint(this.spacePoint, false);
            LineString3d_1.LineString3d.convertLocalToGlobalDetail(cld, candidateIndex, this.context.lineString.numEdges(), this.context.lineString);
            this.context.numPointTest++;
            this.searchState.testAndSave(cld, cld.a);
        }
    }
}
exports.SingleTreeSearchHandlerForClosestPointOnLineString3d = SingleTreeSearchHandlerForClosestPointOnLineString3d;
/**
 * Helper class for searching for the closest approach between linestrings.
 * * Future optimization: avoid sqrt by using squared distance throughout (would require refactoring CurveLocationDetail).
 * @internal
 */
class TwoTreeSearchHandlerForLineString3dLineString3dCloseApproach extends RangeTreeNode_1.TwoTreeDistanceMinimizationSearchHandler {
    /** Context for first polyline */
    contextA;
    /** Context for second polyline */
    contextB;
    /** Search state with current min distance point pair */
    searchState;
    /**
     * Constructor
     * @param contextA captured
     * @param contextB captured
     * @param maxDist collect points at no more than this separation distance
     */
    constructor(contextA, contextB, maxDist) {
        super();
        this.contextA = contextA;
        this.contextB = contextB;
        if (maxDist !== undefined && maxDist < 0)
            maxDist = undefined;
        this.searchState = MinimumValueTester_1.MinimumValueTester.create(maxDist);
    }
    /**
     * Return the current closest approach.
     * * Details contain linestring *and* segment data, cf. [[LineString3d.convertLocalToGlobalDetail]]
     */
    getResult() {
        if (this.searchState.minValue !== undefined) {
            return this.searchState.itemAtMinValue;
        }
        return undefined;
    }
    /** Return the collected close approaches (if collecting) */
    getSavedItems() {
        if (this.searchState.savedItems.length > 0) {
            return this.searchState.savedItems;
        }
        return undefined;
    }
    /** Get current min distance */
    getCurrentDistance() {
        const d = this.searchState.minValue;
        return d === undefined ? Number.MAX_VALUE : d;
    }
    static _workSegmentA;
    static _workSegmentB;
    /** Compute and test the closest approach between two segments, given their indices. */
    processAppDataPair(indexA, indexB) {
        this.contextA.numPointTest++;
        const segA = TwoTreeSearchHandlerForLineString3dLineString3dCloseApproach._workSegmentA =
            this.contextA.lineString.getIndexedSegment(indexA, TwoTreeSearchHandlerForLineString3dLineString3dCloseApproach._workSegmentA);
        const segB = TwoTreeSearchHandlerForLineString3dLineString3dCloseApproach._workSegmentB =
            this.contextB.lineString.getIndexedSegment(indexB, TwoTreeSearchHandlerForLineString3dLineString3dCloseApproach._workSegmentB);
        const cldPair = LineSegment3d_1.LineSegment3d.closestApproach(segA, false, segB, false);
        if (cldPair && this.searchState.isNewMinOrTrigger(cldPair.detailA.a)) {
            LineString3d_1.LineString3d.convertLocalToGlobalDetail(cldPair.detailA, indexA, this.contextA.lineString.numEdges(), this.contextA.lineString);
            LineString3d_1.LineString3d.convertLocalToGlobalDetail(cldPair.detailB, indexB, this.contextB.lineString.numEdges(), this.contextB.lineString);
            this.searchState.testAndSave(cldPair, cldPair.detailA.a);
        }
    }
}
exports.TwoTreeSearchHandlerForLineString3dLineString3dCloseApproach = TwoTreeSearchHandlerForLineString3dLineString3dCloseApproach;
/**
 * Helper class for searching for the closest point in a polyface.
 * * Future optimization: avoid sqrt by using squared distance throughout (would require refactoring FacetLocationDetail).
 * @internal
 */
class SingleTreeSearchHandlerForClosestPointOnPolyface extends RangeTreeNode_1.SingleTreeSearchHandler {
    /** The calling context */
    context;
    /** Evolving search state */
    searchState;
    /** Space point for the search */
    spacePoint;
    /** Whether to include facet interior in search */
    searchFacetInterior;
    /**
     * Constructor
     * @param spacePoint point to test
     * @param context captured
     * @param maxDist collect points at no more than this distance from spacePoint
     * @param searchFacetInterior true: search facet interior + boundary; false: just boundary
     */
    constructor(spacePoint, context, maxDist, searchFacetInterior = false) {
        super();
        this.context = context;
        if (maxDist !== undefined && maxDist < 0)
            maxDist = undefined;
        this.searchState = MinimumValueTester_1.MinimumValueTester.create(maxDist);
        this.spacePoint = Point3dVector3d_1.Point3d.createFrom(spacePoint);
        this.searchFacetInterior = searchFacetInterior;
    }
    /** Return the current closest point */
    getResult() {
        if (this.searchState.minValue !== undefined && this.searchState.itemAtMinValue !== undefined) {
            return this.searchState.itemAtMinValue;
        }
        return undefined;
    }
    /** Return the collected closest points (if collecting) */
    getSavedItems() {
        if (this.searchState.savedItems.length > 0) {
            return this.searchState.savedItems;
        }
        return undefined;
    }
    /**
     * Return true if appData within the range should be offered to `processAppData`.
     * @param range range containing items to be tested.
     * @returns true if the spacePoint is within the range or close enough that a point in the range could be the closest.
     */
    isRangeActive(range) {
        const dMin = range.distanceToPoint(this.spacePoint);
        if (this.searchState.isNewMinValue(dMin)) {
            this.context.numRangeTestTrue++;
            return true;
        }
        this.context.numRangeTestFalse++;
        return false;
    }
    /** Test a facet indexed in the range tree as candidate for "closest" */
    processAppData(candidateIndex) {
        this.context.visitor.setNumWrap(0); // so edgeCount === pointCount; closure point unnecessary for closestPoint[OnBoundary]
        if (this.context.visitor.moveToReadIndex(candidateIndex)) {
            let pld;
            if (this.searchFacetInterior)
                pld = PolygonOps_1.PolygonOps.closestPoint(this.context.visitor.point, this.spacePoint);
            else
                pld = PolygonOps_1.PolygonOps.closestPointOnBoundary(this.context.visitor.point, this.spacePoint);
            this.context.numFacetTest++;
            if (pld && this.searchState.isNewMinOrTrigger(pld.a)) {
                const edgeCount = this.context.visitor.pointCount;
                const fld = this.context.convexFacets
                    ? FacetLocationDetail_1.ConvexFacetLocationDetail.createCapture(this.context.visitor.currentReadIndex(), edgeCount, pld)
                    : FacetLocationDetail_1.NonConvexFacetLocationDetail.createCapture(this.context.visitor.currentReadIndex(), edgeCount, pld);
                this.searchState.testAndSave(fld, pld.a);
            }
        }
    }
}
exports.SingleTreeSearchHandlerForClosestPointOnPolyface = SingleTreeSearchHandlerForClosestPointOnPolyface;
/**
 * Helper class for searching for the closest approach between polyfaces.
 * * Future optimization: avoid sqrt by using squared distance throughout (would require refactoring FacetLocationDetail).
 * @internal
 */
class TwoTreeSearchHandlerForFacetFacetCloseApproach extends RangeTreeNode_1.TwoTreeDistanceMinimizationSearchHandler {
    /** Context for first polyface */
    contextA;
    /** Context for second polyface */
    contextB;
    /** Search state with current min distance and facet pair */
    searchState;
    /** Whether to include facet interior in search */
    searchFacetInterior;
    /** Constructor
     * @param contextA captured
     * @param contextB captured
     * @param maxDist collect points at no more than this separation distance
     * @param searchFacetInterior true: search facet interior + boundary; false: just boundary
    */
    constructor(contextA, contextB, maxDist, searchFacetInterior = false) {
        super();
        this.contextA = contextA;
        this.contextB = contextB;
        if (maxDist !== undefined && maxDist < 0)
            maxDist = undefined;
        this.searchState = MinimumValueTester_1.MinimumValueTester.create(maxDist);
        this.searchFacetInterior = searchFacetInterior && contextA.convexFacets && contextB.convexFacets;
    }
    /** Return the facets with closest approach */
    getResult() {
        if (this.searchState.minValue !== undefined) {
            return this.searchState.itemAtMinValue;
        }
        return undefined;
    }
    /** Return the collected close approaches (if collecting) */
    getSavedItems() {
        if (this.searchState.savedItems.length > 0) {
            return this.searchState.savedItems;
        }
        return undefined;
    }
    /** Get current min distance */
    getCurrentDistance() {
        const d = this.searchState.minValue;
        return d === undefined ? Number.MAX_VALUE : d;
    }
    /** Compute and test the closest approach between two facets, given their indices. */
    processAppDataPair(indexA, indexB) {
        this.contextA.visitor.setNumWrap(1); // closed polygons are more efficient for PolygonOps.closestApproach
        this.contextB.visitor.setNumWrap(1);
        if (this.contextA.visitor.moveToReadIndex(indexA) && this.contextB.visitor.moveToReadIndex(indexB)) {
            // ASSUME: not worth sending in maxDist here...
            const pldPair = PolygonOps_1.PolygonOps.closestApproach(this.contextA.visitor.point, this.contextB.visitor.point, undefined, this.searchFacetInterior);
            this.contextA.numFacetTest++;
            if (pldPair && this.searchState.isNewMinOrTrigger(pldPair.detailA.a)) {
                const edgeCountA = this.contextA.visitor.pointCount - 1;
                const edgeCountB = this.contextB.visitor.pointCount - 1;
                const fldPair = FacetLocationDetail_1.FacetLocationDetailPair.create(this.contextA.convexFacets ? FacetLocationDetail_1.ConvexFacetLocationDetail.createCapture(indexA, edgeCountA, pldPair.detailA) : FacetLocationDetail_1.NonConvexFacetLocationDetail.createCapture(indexA, edgeCountA, pldPair.detailA), this.contextB.convexFacets ? FacetLocationDetail_1.ConvexFacetLocationDetail.createCapture(indexB, edgeCountB, pldPair.detailB) : FacetLocationDetail_1.NonConvexFacetLocationDetail.createCapture(indexB, edgeCountB, pldPair.detailB));
                this.searchState.testAndSave(fldPair, fldPair.detailA.a);
            }
        }
    }
}
exports.TwoTreeSearchHandlerForFacetFacetCloseApproach = TwoTreeSearchHandlerForFacetFacetCloseApproach;
//# sourceMappingURL=RangeTreeSearchHandlers.js.map