"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.IndexedPolyfaceSubsetVisitor = exports.IndexedPolyfaceVisitor = void 0;
/** @packageDocumentation
 * @module Polyface
 */
const Geometry_1 = require("../Geometry");
const Angle_1 = require("../geometry3d/Angle");
const Point3dVector3d_1 = require("../geometry3d/Point3dVector3d");
const PolygonOps_1 = require("../geometry3d/PolygonOps");
const Polyface_1 = require("./Polyface");
const PolyfaceData_1 = require("./PolyfaceData");
/**
 * An `IndexedPolyfaceVisitor` is an iterator-like object that "visits" facets of a mesh.
 * * The visitor extends `PolyfaceData`, so it can at any time hold all the data of a single facet.
 * @public
 */
class IndexedPolyfaceVisitor extends PolyfaceData_1.PolyfaceData {
    _currentFacetIndex;
    _nextFacetIndex;
    _numWrap;
    _numEdges;
    _polyface;
    // to be called from static factory method that validates the polyface
    constructor(polyface, numWrap) {
        super(polyface.data.normalCount > 0, polyface.data.paramCount > 0, polyface.data.colorCount > 0, polyface.twoSided);
        this._polyface = polyface;
        this._numWrap = numWrap;
        if (polyface.data.auxData)
            this.auxData = polyface.data.auxData.createForVisitor();
        if (polyface.data.edgeMateIndex)
            this.edgeMateIndex = [];
        this._numEdges = 0;
        this._nextFacetIndex = 0;
        this._currentFacetIndex = -1;
        this.reset();
    }
    /** Return the client polyface object. */
    clientPolyface() {
        return this._polyface;
    }
    /** Return the number of facets this visitor is able to visit. */
    getVisitableFacetCount() {
        return this._polyface.facetCount;
    }
    /**
     * Set the number of vertices replicated in visitor arrays (both data and index arrays).
     * * 0,1,2 are the most common as numWrap.
     * * Example: suppose `[6,7,8]` is the pointIndex array representing a triangle. First edge would be `6,7`. Second
     * edge is `7,8`. Third edge is `8,6`. To access `6` for the third edge, we have to go back to the start of array.
     * Therefore, it is useful to store `6` at the end of pointIndex array, i.e., `[6,7,8,6]` meaning `numWrap = 1`.
     * Continuing this example, `numWrap = 2` (i.e., `[6,7,8,6,7]`) is useful when each vertex visit requires the next
     * two points, e.g., to form two adjacent vectors for a cross product.
     */
    setNumWrap(numWrap) {
        this._numWrap = numWrap;
    }
    /**
     * Return the number of edges in the current facet.
     * * If `numWrap > 0` for this visitor, the number of edges is smaller than the number of points.
     */
    get numEdgesThisFacet() {
        return this._numEdges;
    }
    /** Create a visitor for iterating the facets of `polyface`. */
    static create(polyface, numWrap) {
        return new IndexedPolyfaceVisitor(polyface, numWrap);
    }
    /** Advance the iterator to a particular facet in the client polyface. */
    moveToReadIndex(facetIndex) {
        if (!this._polyface.isValidFacetIndex(facetIndex))
            return false;
        const numEdges = this._polyface.numEdgeInFacet(facetIndex);
        if (this._currentFacetIndex !== facetIndex || numEdges + this._numWrap !== this.pointCount) {
            this._currentFacetIndex = facetIndex;
            this._numEdges = numEdges;
            this.resizeAllArrays(this._numEdges + this._numWrap);
            this.gatherIndexedData(this._polyface.data, this._polyface.facetIndex0(this._currentFacetIndex), this._polyface.facetIndex1(this._currentFacetIndex), this._numWrap);
        }
        this._nextFacetIndex = facetIndex + 1;
        return true;
    }
    /** Advance the iterator to a the 'next' facet in the client polyface. */
    moveToNextFacet() {
        if (this._nextFacetIndex !== this._currentFacetIndex)
            return this.moveToReadIndex(this._nextFacetIndex);
        this._nextFacetIndex++;
        return true;
    }
    /** Restart the visitor at the first facet. */
    reset() {
        this.moveToReadIndex(0);
        this._nextFacetIndex = 0; // so immediate moveToNextFacet stays here.
    }
    /**
     * Attempts to extract the distance parameter for the given vertex `index` on the current facet.
     * Returns the distance parameter as a point. Returns `undefined` on failure.
     */
    tryGetDistanceParameter(index, result) {
        if (index < 0 || index >= this.numEdgesThisFacet)
            return undefined;
        if (this.param === undefined || this._polyface.data.face.length === 0)
            return undefined;
        const faceData = this._polyface.tryGetFaceData(this._currentFacetIndex);
        if (!faceData)
            return undefined;
        return faceData.convertParamXYToDistance(this.param.getXAtUncheckedPointIndex(index), this.param.getYAtUncheckedPointIndex(index), result);
    }
    /**
     * Attempts to extract the normalized parameter (0,1) for the given vertex `index` on the current facet.
     * Returns the normalized parameter as a point. Returns `undefined` on failure.
     */
    tryGetNormalizedParameter(index, result) {
        if (index < 0 || index >= this.numEdgesThisFacet)
            return undefined;
        if (this.param === undefined || this._polyface.data.face.length === 0)
            return undefined;
        const faceData = this._polyface.tryGetFaceData(this._currentFacetIndex);
        if (!faceData)
            return undefined;
        return faceData.convertParamXYToNormalized(this.param.getXAtUncheckedPointIndex(index), this.param.getYAtUncheckedPointIndex(index), result);
    }
    /** Return the facetIndex of the currently referenced facet in the client polyface. */
    currentReadIndex() {
        return this._currentFacetIndex;
    }
    /** Return the point index of vertex `i` within the currently loaded facet. */
    clientPointIndex(i) {
        return this.pointIndex[i];
    }
    /** Return the param index of vertex `i` within the currently loaded facet. */
    clientParamIndex(i) {
        return this.paramIndex ? this.paramIndex[i] : -1;
    }
    /** Return the normal index of vertex `i` within the currently loaded facet. */
    clientNormalIndex(i) {
        return this.normalIndex ? this.normalIndex[i] : -1;
    }
    /** Return the color index of vertex `i` within the currently loaded facet. */
    clientColorIndex(i) {
        return this.colorIndex ? this.colorIndex[i] : -1;
    }
    /** Return the aux data index of vertex `i` within the currently loaded facet. */
    clientAuxIndex(i) {
        return this.auxData ? this.auxData.indices[i] : -1;
    }
    /** Clear the contents of the data arrays. */
    clearArrays() {
        this.point.length = 0;
        this.edgeVisible.length = 0;
        if (this.param !== undefined)
            this.param.length = 0;
        if (this.normal !== undefined)
            this.normal.length = 0;
        if (this.color !== undefined)
            this.color.length = 0;
        // TODO: auxData? taggedNumericData?
    }
    /** Transfer data from a specified `index` of the `other` visitor as new data in this visitor. */
    pushDataFrom(other, index) {
        this.point.pushFromGrowableXYZArray(other.point, index);
        this.edgeVisible.push(other.edgeVisible[index]);
        if (this.param && other.param && index < other.param.length)
            this.param.pushFromGrowableXYArray(other.param, index);
        if (this.normal && other.normal && index < other.normal.length)
            this.normal.pushFromGrowableXYZArray(other.normal, index);
        if (this.color && other.color && index < other.color.length)
            this.color.push(other.color[index]);
        // TODO: auxData? taggedNumericData?
    }
    /**
     * Transfer interpolated data from the other visitor.
     * * All data values are interpolated at `fraction` between `other` values at `index0` and `index1`.
     */
    pushInterpolatedDataFrom(other, index0, fraction, index1) {
        if (index0 > index1)
            this.pushInterpolatedDataFrom(other, index1, 1.0 - fraction, index0);
        this.point.pushInterpolatedFromGrowableXYZArray(other.point, index0, fraction, index1);
        const newVisibility = (((index0 + 1) % other.edgeVisible.length) === index1) ? other.edgeVisible[index0] : false;
        this.edgeVisible.push(newVisibility); // interpolation along an edge preserves visibility of original edge
        if (this.param && other.param && index0 < other.param.length && index1 < other.param.length)
            this.param.pushInterpolatedFromGrowableXYArray(other.param, index0, fraction, index1);
        if (this.normal && other.normal && index0 < other.normal.length && index1 < other.normal.length)
            this.normal.pushInterpolatedFromGrowableXYZArray(other.normal, index0, fraction, index1);
        if (this.color && other.color && index0 < other.color.length && index1 < other.color.length)
            this.color.push(Geometry_1.Geometry.interpolateColor(other.color[index0], fraction, other.color[index1]));
        // TODO: auxData? taggedNumericData?
    }
    /** Create a visitor for a subset of the facets visitable by the instance. */
    createSubsetVisitor(facetIndices, numWrap = 0) {
        return IndexedPolyfaceSubsetVisitor.createSubsetVisitor(this._polyface, facetIndices, numWrap);
    }
}
exports.IndexedPolyfaceVisitor = IndexedPolyfaceVisitor;
/**
 * An `IndexedPolyfaceSubsetVisitor` is an `IndexedPolyfaceVisitor` which only visits a subset of facets in the polyface.
 * * The subset is defined by an array of facet indices provided when this visitor is created.
 * * Input indices (e.g., for `moveToReadIndex`) are understood to be indices into the subset array.
 * @public
 */
class IndexedPolyfaceSubsetVisitor extends IndexedPolyfaceVisitor {
    _facetIndices;
    _currentSubsetIndex; // index within _facetIndices
    _nextSubsetIndex; // index within _facetIndices
    constructor(polyface, facetIndices, numWrap) {
        super(polyface, numWrap);
        this._facetIndices = facetIndices.slice();
        this._currentSubsetIndex = -1;
        this._nextSubsetIndex = 0;
        this.reset();
    }
    isValidSubsetIndex(index) {
        return index >= 0 && index < this._facetIndices.length;
    }
    /**
     * Create a visitor for iterating a subset of the facets of `polyface`.
     * @param polyface reference to the client polyface, supplying facets
     * @param facetIndices array of indices of facets in the client polyface to visit. This array is cloned.
     * @param numWrap number of vertices replicated in the visitor arrays to facilitate simpler caller code. Default is zero.
     */
    static createSubsetVisitor(polyface, facetIndices, numWrap = 0) {
        return new IndexedPolyfaceSubsetVisitor(polyface, facetIndices, numWrap);
    }
    /**
     * Advance the iterator to a particular facet in the subset of client polyface facets.
     * @param subsetIndex index into the subset array, not to be confused with the client facet index.
     * @return whether the iterator was successfully moved.
     */
    moveToReadIndex(subsetIndex) {
        if (this.isValidSubsetIndex(subsetIndex)) {
            this._currentSubsetIndex = subsetIndex;
            this._nextSubsetIndex = subsetIndex + 1;
            return super.moveToReadIndex(this._facetIndices[subsetIndex]);
        }
        return false;
    }
    /**
     * Advance the iterator to the next facet in the subset of client polyface facets.
     * @return whether the iterator was successfully moved.
     */
    moveToNextFacet() {
        if (this._nextSubsetIndex !== this._currentSubsetIndex)
            return this.moveToReadIndex(this._nextSubsetIndex);
        this._nextSubsetIndex++;
        return true;
    }
    /** Restart the visitor at the first facet. */
    reset() {
        if (this._facetIndices) { // avoid crash during super ctor when we aren't yet initialized
            this.moveToReadIndex(0);
            this._nextSubsetIndex = 0; // so immediate moveToNextFacet stays here.
        }
    }
    /**
     * Return the client polyface facet index (aka "readIndex") for the given subset index.
     * @param subsetIndex index into the subset array. Default is the subset index of the currently visited facet.
     * @return valid client polyface facet index, or `undefined` if invalid subset index.
     */
    parentFacetIndex(subsetIndex) {
        if (undefined === subsetIndex)
            subsetIndex = this._currentSubsetIndex;
        return this.isValidSubsetIndex(subsetIndex) ? this._facetIndices[subsetIndex] : undefined;
    }
    /** Return the number of facets this visitor is able to visit. */
    getVisitableFacetCount() {
        return this._facetIndices.length;
    }
    /**
     * Create a visitor for those mesh facets with normal in the same half-space as the given vector.
     * * For example, to visit the top facets of a tiled terrain mesh but skip the "skirt" facets, pass
     * `compareVector = Vector3d.unitZ()` and a suitable `sideAngle` tolerance. Note that this will also
     * filter out *interior* facets that are nearly vertical, not just the "skirt" facets on the boundary.
     * @param mesh the mesh from which to select facets
     * @param compareVector vector to which to compare facet normals. The visitor will visit only those facets
     * with normals in the same half-space as this vector. Default is 001.
     * @param sideAngle optional angular tolerance to filter the facets near the border between half-spaces.
     * The visitor will *not* visit facets whose normals are nearly perpendicular to `compareVector`.
     * Default is [[Geometry.smallAngleRadians]].
     * @param numWrap optional number of entries replicated in visitor arrays. Default is 0.
    */
    static createNormalComparison(mesh, compareVector = Point3dVector3d_1.Vector3d.unitZ(), sideAngle = Angle_1.Angle.createSmallAngle(), numWrap = 0) {
        if (mesh instanceof Polyface_1.IndexedPolyface)
            return this.createNormalComparison(mesh.createVisitor(), compareVector, sideAngle, numWrap);
        const visitor = mesh;
        const facets = [];
        const facetNormal = Point3dVector3d_1.Vector3d.createZero();
        for (visitor.reset(); visitor.moveToNextFacet();) {
            if (!PolygonOps_1.PolygonOps.unitNormal(visitor.point, facetNormal))
                continue; // degenerate facet
            if (facetNormal.dotProduct(compareVector) < 0.0)
                continue; // ignore facet facing other half-space
            if (facetNormal.angleFromPerpendicular(compareVector).isMagnitudeLessThanOrEqual(sideAngle))
                continue; // ignore side facet
            facets.push(visitor.currentReadIndex());
        }
        return IndexedPolyfaceSubsetVisitor.createSubsetVisitor(visitor.clientPolyface(), facets, numWrap);
    }
}
exports.IndexedPolyfaceSubsetVisitor = IndexedPolyfaceSubsetVisitor;
//# sourceMappingURL=IndexedPolyfaceVisitor.js.map