/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Polyface
 */
import { Geometry } from "../Geometry";
import { GrowableXYArray } from "../geometry3d/GrowableXYArray";
import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray";
import { Point2d } from "../geometry3d/Point2dVector2d";
import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d";
import { NumberArray } from "../geometry3d/PointHelpers";
import { Range3d } from "../geometry3d/Range";
import { ClusterableArray } from "../numerics/ClusterableArray";
import { PolyfaceAuxData } from "./AuxData";
import { TaggedNumericData } from "./TaggedNumericData";
// cspell:word internaldocs
/**
 * `PolyfaceData` carries data arrays for point, normal, uv-parameters, and color, and index arrays for each.
 * * Normal, uv-parameter, and color data are optional.
 * * A given data array is defined if and only if its corresponding index array is defined.
 * * All defined index arrays have parallel face loop order and structure, and thus the same length.
 * * `IndexedPolyface` carries a PolyfaceData as a member (NOT as a base class; it already has `GeometryQuery` as base).
 * * `IndexedPolyfaceVisitor` uses PolyfaceData as a base class. In this use, there is only a single facet in `PolyfaceData`.
 * * `PolyfaceData` does not know what index range constitutes a given facet. This is managed by a derived/carrier class.
 * @public
 */
export class PolyfaceData {
    /**
     * Relative tolerance used in tests for planar facets.
     * @internal
    */
    static planarityLocalRelTol = 1.0e-13;
    /** Coordinate data for points in the facets (packed as numbers in a contiguous array). */
    point;
    /** Indices of points at facet vertices. */
    pointIndex;
    /** Coordinates of normal vectors (packed as numbers in a contiguous array). */
    normal;
    /** Indices of normals at facet vertices. */
    normalIndex;
    /** Coordinates of uv parameters (packed as numbers in a contiguous array). */
    param;
    /** Indices of params at facet vertices. */
    paramIndex;
    /**
     * Color values. These are carried around as simple numbers, but are probably required (by display systems) to map
     * exactly to 32-bit integers.
     */
    color;
    /** Indices of colors at facet vertices. */
    colorIndex;
    /**
     * Map from facet index to face data.
     * * A "face" is a logical grouping of connected facets in the mesh, e.g., the facets that resulted from faceting
     * a given face of a solid.
     * * Face data remains empty until a face is specified.
     */
    face;
    /** Auxiliary data. */
    auxData;
    /** Tagged geometry data. */
    taggedNumericData;
    /**
     * Booleans indicating visibility of corresponding edges.
     * * The `edgeVisible` array is parallel to the `pointIndex` array.
     * * The visibility flag applies to the edge whose start vertex index appears in the same place in the `pointIndex` array.
     * * For example, consider the mesh with 2 triangular facets given by `pointIndex = [1,0,2, 1,2,3]`. If the triangles'
     * shared edge is hidden, then the mesh has `edgeVisible = [true,true,false, false,true,true]`.
     */
    edgeVisible;
    /** The [[twoSided]] flag. */
    _twoSided;
    /**
     * Flag indicating if the mesh closure is unknown (0), open sheet (1), closed solid (2).
     * * A boundary edge of a mesh is defined as an edge with only one connected facet.
     * * Closed solid is a mesh with no boundary edge. Open sheet is a mesh that has boundary edge(s).
     */
    _expectedClosure;
    /**
     * Optional index array for moving "across an edge" to an adjacent facet.
     * * This array:
     *   * completes the topology of the polyface.
     *   * has the same length as the other PolyfaceData index arrays.
     *   * is populated by [[IndexedPolyfaceWalker.buildEdgeMateIndices]].
     *   * is used by [[IndexedPolyfaceWalker]] to traverse the polyface.
     *   * is invalid if the polyface topology is subsequently changed.
     * * Let k1 = edgeMateIndex[k] be defined. Then:
     *   * k1 is an index (an "edge index") into the PolyfaceData index arrays. (The same for k.)
     *   * k and k1 refer to the two oppositely oriented sides of an interior edge in the polyface.
     *   * pointIndex[k1] is the point at the opposite end of the edge that starts at pointIndex[k].
     *   * edgeMateIndex[k1] === k.
     * * If k1 is undefined, then there is no adjacent facet across the edge that starts at pointIndex[k],
     * i.e. k refers to a boundary edge.
     */
    edgeMateIndex;
    /**
     * Dereference the edgeMateIndex array.
     * * This method returns undefined if:
     *   * k is undefined
     *   * `this.edgeMateIndex` is undefined
     *   * k is out of bounds for `this.edgeMateIndex`
     *   * `this.edgeMateIndex[k]` is undefined
     */
    edgeIndexToEdgeMateIndex(k) {
        if (k !== undefined
            && this.edgeMateIndex !== undefined
            && k >= 0 && k < this.edgeMateIndex.length)
            return this.edgeMateIndex[k];
        return undefined;
    }
    /** Test if `value` is a valid index into the `pointIndex` array. */
    isValidEdgeIndex(value) {
        return value !== undefined && value >= 0 && value < this.pointIndex.length;
    }
    /**
     * Constructor for facets.
     * @param needNormals `true` to allocate empty normal data and index arrays; `false` (default) to leave undefined.
     * @param needParams `true` to allocate empty uv parameter data and index arrays; `false` (default) to leave undefined.
     * @param needColors `true` to allocate empty color data and index arrays; `false` (default) to leave undefined.
     * @param twoSided `true` (default) if the facets are to be considered viewable from the back; `false` if they are amenable to backface culling.
     */
    constructor(needNormals = false, needParams = false, needColors = false, twoSided = true) {
        this.point = new GrowableXYZArray();
        this.pointIndex = [];
        if (needNormals) {
            this.normal = new GrowableXYZArray();
            this.normalIndex = [];
        }
        if (needParams) {
            this.param = new GrowableXYArray();
            this.paramIndex = [];
        }
        if (needColors) {
            this.color = [];
            this.colorIndex = [];
        }
        this.face = [];
        this.edgeVisible = [];
        this._twoSided = twoSided;
        this._expectedClosure = 0;
    }
    /** Return a deep clone. */
    clone() {
        const result = new PolyfaceData();
        result.point = this.point.clone();
        result.pointIndex = this.pointIndex.slice();
        if (this.normal)
            result.normal = this.normal.clone();
        if (this.normalIndex)
            result.normalIndex = this.normalIndex.slice();
        if (this.param)
            result.param = this.param.clone();
        if (this.paramIndex)
            result.paramIndex = this.paramIndex.slice();
        if (this.color)
            result.color = this.color.slice();
        if (this.colorIndex)
            result.colorIndex = this.colorIndex.slice();
        result.face = this.face.slice();
        if (this.auxData)
            result.auxData = this.auxData.clone();
        if (this.taggedNumericData)
            result.taggedNumericData = this.taggedNumericData.clone();
        result.edgeVisible = this.edgeVisible.slice();
        result.twoSided = this.twoSided;
        result.expectedClosure = this.expectedClosure;
        if (this.edgeMateIndex)
            result.edgeMateIndex = this.edgeMateIndex.slice();
        return result;
    }
    /** Test for equal indices and nearly equal coordinates. */
    isAlmostEqual(other) {
        if (!GrowableXYZArray.isAlmostEqual(this.point, other.point))
            return false;
        if (!NumberArray.isExactEqual(this.pointIndex, other.pointIndex))
            return false;
        if (!GrowableXYZArray.isAlmostEqual(this.normal, other.normal))
            return false;
        if (!NumberArray.isExactEqual(this.normalIndex, other.normalIndex))
            return false;
        if (!GrowableXYArray.isAlmostEqual(this.param, other.param))
            return false;
        if (!NumberArray.isExactEqual(this.paramIndex, other.paramIndex))
            return false;
        if (!NumberArray.isExactEqual(this.color, other.color))
            return false;
        if (!NumberArray.isExactEqual(this.colorIndex, other.colorIndex))
            return false;
        if (!PolyfaceAuxData.isAlmostEqual(this.auxData, other.auxData))
            return false;
        if (!TaggedNumericData.areAlmostEqual(this.taggedNumericData, other.taggedNumericData))
            return false;
        if (!NumberArray.isExactEqual(this.edgeVisible, other.edgeVisible))
            return false;
        if (this.twoSided !== other.twoSided)
            return false;
        if (this.expectedClosure !== other.expectedClosure)
            return false;
        if (!NumberArray.isExactEqual(this.edgeMateIndex, other.edgeMateIndex))
            return false;
        return true;
    }
    /** Ask if normals are required in this mesh. */
    get requireNormals() {
        return undefined !== this.normal;
    }
    /** Ask if params are required in this mesh. */
    get requireParams() {
        return undefined !== this.param;
    }
    /** Ask if colors are required in this mesh. */
    get requireColors() {
        return undefined !== this.color;
    }
    /** Get the point count */
    get pointCount() {
        return this.point.length;
    }
    /** Get the normal count */
    get normalCount() {
        return this.normal ? this.normal.length : 0;
    }
    /** Get the param count */
    get paramCount() {
        return this.param ? this.param.length : 0;
    }
    /** Get the color count */
    get colorCount() {
        return this.color ? this.color.length : 0;
    }
    /** Get the index count. Note that the point array is always indexed, and index arrays all have the same length. */
    get indexCount() {
        return this.pointIndex.length;
    }
    /**
     * Get the number of faces.
     * * Note that a "face" is not a facet.
     * * A face is a subset of the Polyface's facets grouped for application purposes.
     */
    get faceCount() {
        return this.face.length;
    }
    /** Return indexed point at index `i`. This is a COPY of the coordinates, not a reference. */
    getPoint(i, result) {
        return this.point.getPoint3dAtCheckedPointIndex(i, result);
    }
    /** Return indexed normal at index `i`. This is a COPY of the normal, not a reference. */
    getNormal(i, result) {
        return this.normal ? this.normal.getVector3dAtCheckedVectorIndex(i, result) : undefined;
    }
    /** Return indexed param at index `i`. This is a COPY of the coordinates, not a reference. */
    getParam(i, result) {
        return this.param ? this.param.getPoint2dAtCheckedPointIndex(i, result) : undefined;
    }
    /** Return indexed color at index `i`. Index `i` is not checked for validity. */
    getColor(i) {
        return this.color ? this.color[i] : 0;
    }
    /** Return indexed visibility. at index `i`. Index `i` is not checked for validity. */
    getEdgeVisible(i) {
        return this.edgeVisible[i];
    }
    /**
     * Boolean flag indicating if the facets are viewable from the back.
     * * Default value is true.
     * * Set to false only if the mesh is known to be a closed volume with outward normals,
     * indicating it is amenable to backface culling for improved display performance.
     */
    get twoSided() {
        return this._twoSided;
    }
    set twoSided(value) {
        this._twoSided = value;
    }
    /** Get flag indicating if the mesh closure is unknown (0), open sheet (1), closed solid (2). */
    get expectedClosure() {
        return this._expectedClosure;
    }
    set expectedClosure(value) {
        this._expectedClosure = value;
    }
    /** Set the tagged geometry data. */
    setTaggedNumericData(data) {
        this.taggedNumericData = data;
    }
    /** Copy the contents (not pointer) of `point[i]` into `dest`. Index `i` is not checked for validity. */
    copyPointTo(i, dest) {
        this.point.getPoint3dAtUncheckedPointIndex(i, dest);
    }
    /** Copy the contents (not pointer) of `normal[i]` into `dest`. Index `i` is not checked for validity. */
    copyNormalTo(i, dest) {
        if (this.normal)
            this.normal.getVector3dAtUncheckedVectorIndex(i, dest);
    }
    /** Copy the contents (not pointer) of `param[i]` into `dest`. Index `i` is not checked for validity. */
    copyParamTo(i, dest) {
        if (this.param)
            this.param.getPoint2dAtUncheckedPointIndex(i, dest);
    }
    /** Test if param at a index `i` matches the given uv */
    isAlmostEqualParamIndexUV(i, u, v) {
        if (this.param !== undefined && i >= 0 && i < this.param.length)
            return Geometry.isSameCoordinate(u, this.param.getXAtUncheckedPointIndex(i))
                && Geometry.isSameCoordinate(v, this.param.getYAtUncheckedPointIndex(i));
        return false;
    }
    /**
     * Copy data from `other` to `this`.
     * * This is the essence of transferring coordinates spread throughout a large polyface into a visitor's single facet.
     * * Common usage: "other" is a Polyface, "this" is a PolyfaceVisitor to receive data from a single facet of the Polyface.
     * * Does NOT copy face data - visitors reference the FacetFaceData array for the whole polyface.
     * @param other polyface data being mined.
     * @param index0 start index in other's index arrays.
     * @param index1 end index (one beyond last data accessed) in other's index arrays.
     * @param numWrap number of points to replicate as wraparound.
     */
    gatherIndexedData(other, index0, index1, numWrap) {
        const numEdge = index1 - index0;
        if (numWrap > numEdge)
            numWrap = numEdge;
        const numTotal = numEdge + numWrap;
        this.resizeAllArrays(numTotal);
        // copy wrapped points
        for (let i = 0; i < numEdge; i++)
            this.point.transferFromGrowableXYZArray(i, other.point, other.pointIndex[index0 + i]);
        for (let i = 0; i < numWrap; i++)
            this.point.transferFromGrowableXYZArray(numEdge + i, this.point, i);
        // copy wrapped pointIndex
        for (let i = 0; i < numEdge; i++)
            this.pointIndex[i] = other.pointIndex[index0 + i];
        for (let i = 0; i < numWrap; i++)
            this.pointIndex[numEdge + i] = this.pointIndex[i];
        // copy wrapped normals
        if (this.normal && this.normalIndex && other.normal && other.normalIndex) {
            for (let i = 0; i < numEdge; i++)
                this.normal.transferFromGrowableXYZArray(i, other.normal, other.normalIndex[index0 + i]);
            for (let i = 0; i < numWrap; i++)
                this.normal.transferFromGrowableXYZArray(numEdge + i, this.normal, i);
            // copy wrapped normalIndex
            for (let i = 0; i < numEdge; i++)
                this.normalIndex[i] = other.normalIndex[index0 + i];
            for (let i = 0; i < numWrap; i++)
                this.normalIndex[numEdge + i] = this.normalIndex[i];
        }
        // copy wrapped params
        if (this.param && this.paramIndex && other.param && other.paramIndex) {
            for (let i = 0; i < numEdge; i++)
                this.param.transferFromGrowableXYArray(i, other.param, other.paramIndex[index0 + i]);
            for (let i = 0; i < numWrap; i++)
                this.param.transferFromGrowableXYArray(numEdge + i, this.param, i);
            // copy wrapped paramIndex
            for (let i = 0; i < numEdge; i++)
                this.paramIndex[i] = other.paramIndex[index0 + i];
            for (let i = 0; i < numWrap; i++)
                this.paramIndex[numEdge + i] = this.paramIndex[i];
        }
        // copy wrapped colors
        if (this.color && this.colorIndex && other.color && other.colorIndex) {
            for (let i = 0; i < numEdge; i++)
                this.color[i] = other.color[other.colorIndex[index0 + i]];
            for (let i = 0; i < numWrap; i++)
                this.color[numEdge + i] = this.color[i];
            // copy wrapped colorIndex
            for (let i = 0; i < numEdge; i++)
                this.colorIndex[i] = other.colorIndex[index0 + i];
            for (let i = 0; i < numWrap; i++)
                this.colorIndex[numEdge + i] = this.colorIndex[i];
        }
        // copy wrapped edge visibility
        for (let i = 0; i < numEdge; i++)
            this.edgeVisible[i] = other.edgeVisible[index0 + i];
        for (let i = 0; i < numWrap; i++)
            this.edgeVisible[numEdge + i] = this.edgeVisible[i];
        // copy wrapped auxData
        if (this.auxData && other.auxData && this.auxData.channels.length === other.auxData.channels.length) {
            for (let iChannel = 0; iChannel < this.auxData.channels.length; iChannel++) {
                const thisChannel = this.auxData.channels[iChannel];
                const otherChannel = other.auxData.channels[iChannel];
                const blockSize = thisChannel.entriesPerValue;
                if (thisChannel.data.length === otherChannel.data.length) {
                    for (let iData = 0; iData < thisChannel.data.length; iData++) {
                        const thisData = thisChannel.data[iData];
                        const otherData = otherChannel.data[iData];
                        for (let i = 0; i < numEdge; i++)
                            thisData.copyValues(otherData, i, other.auxData.indices[index0 + i], blockSize);
                        for (let i = 0; i < numWrap; i++)
                            thisData.copyValues(thisData, other.auxData.indices[numEdge + i], i, blockSize);
                    }
                }
            }
            // copy wrapped auxData index
            for (let i = 0; i < numEdge; i++)
                this.auxData.indices[i] = other.auxData.indices[index0 + i];
            for (let i = 0; i < numWrap; i++)
                this.auxData.indices[numEdge + i] = this.auxData.indices[i];
        }
        // copy wrapped edgeMateIndex
        if (this.edgeMateIndex && other.edgeMateIndex) {
            for (let i = 0; i < numEdge; i++)
                this.edgeMateIndex[i] = other.edgeMateIndex[index0 + i];
            for (let i = 0; i < numWrap; i++)
                this.edgeMateIndex[numEdge + i] = this.edgeMateIndex[i];
        }
    }
    /** Trim the `data` arrays to the stated `length`. */
    static trimArray(data, length) {
        if (data && length < data.length)
            data.length = length;
    }
    /**
     * Trim all index arrays to the stated `length`.
     * * This is called by PolyfaceBuilder to clean up after an aborted construction sequence.
     */
    trimAllIndexArrays(length) {
        PolyfaceData.trimArray(this.pointIndex, length);
        PolyfaceData.trimArray(this.paramIndex, length);
        PolyfaceData.trimArray(this.normalIndex, length);
        PolyfaceData.trimArray(this.colorIndex, length);
        PolyfaceData.trimArray(this.edgeVisible, length);
        if (this.auxData) {
            PolyfaceData.trimArray(this.auxData.indices, length);
            for (const channel of this.auxData.channels) {
                for (const data of channel.data)
                    PolyfaceData.trimArray(data.values, channel.entriesPerValue * length);
            }
        }
        PolyfaceData.trimArray(this.edgeMateIndex, length);
    }
    /**
     * Resize all data and index arrays to the specified `length`.
     * * This is used by visitors, whose data and index arrays are all parallel.
     */
    resizeAllArrays(length) {
        if (length > this.point.length) {
            while (this.point.length < length)
                this.point.push(Point3d.create());
            while (this.pointIndex.length < length)
                this.pointIndex.push(-1);
            if (this.normal)
                while (this.normal.length < length)
                    this.normal.push(Vector3d.create());
            if (this.normalIndex)
                while (this.normalIndex.length < length)
                    this.normalIndex.push(-1);
            if (this.param)
                while (this.param.length < length)
                    this.param.push(Point2d.create());
            if (this.paramIndex)
                while (this.paramIndex.length < length)
                    this.paramIndex.push(-1);
            if (this.color)
                while (this.color.length < length)
                    this.color.push(0);
            if (this.colorIndex)
                while (this.colorIndex.length < length)
                    this.colorIndex.push(-1);
            while (this.edgeVisible.length < length)
                this.edgeVisible.push(false);
            if (this.auxData) {
                for (const channel of this.auxData.channels) {
                    for (const channelData of channel.data) {
                        while (channelData.values.length < length * channel.entriesPerValue)
                            channelData.values.push(0);
                    }
                }
                if (this.auxData.indices)
                    this.auxData.indices.push(-1);
            }
            if (this.edgeMateIndex)
                while (this.edgeMateIndex.length < length)
                    this.edgeMateIndex.push(undefined);
        }
        else if (length < this.point.length) {
            this.point.resize(length);
            this.pointIndex.length = length;
            if (this.normal)
                this.normal.resize(length);
            if (this.normalIndex)
                this.normalIndex.length = length;
            if (this.param)
                this.param.resize(length);
            if (this.paramIndex)
                this.paramIndex.length = length;
            if (this.color)
                this.color.length = length;
            if (this.colorIndex)
                this.colorIndex.length = length;
            this.edgeVisible.length = length;
            if (this.auxData) {
                for (const channel of this.auxData.channels) {
                    for (const channelData of channel.data) {
                        channelData.values.length = length * channel.entriesPerValue;
                    }
                }
                if (this.auxData.indices)
                    this.auxData.indices.length = length;
            }
            if (this.edgeMateIndex)
                this.edgeMateIndex.length = length;
        }
    }
    /**
     * Resize all data arrays to the specified `length`.
     * @deprecated in 4.5.0 - will not be removed until after 2026-06-13. Because name is misleading. Call [[PolyfaceData.resizeAllArrays]] instead.
     */
    resizeAllDataArrays(length) {
        if (length > this.point.length) {
            while (this.point.length < length)
                this.point.push(Point3d.create());
            while (this.pointIndex.length < length)
                this.pointIndex.push(-1);
            while (this.edgeVisible.length < length)
                this.edgeVisible.push(false);
            if (this.normal)
                while (this.normal.length < length)
                    this.normal.push(Vector3d.create());
            if (this.param)
                while (this.param.length < length)
                    this.param.push(Point2d.create());
            if (this.color)
                while (this.color.length < length)
                    this.color.push(0);
            if (this.auxData) {
                for (const channel of this.auxData.channels) {
                    for (const channelData of channel.data) {
                        while (channelData.values.length < length * channel.entriesPerValue)
                            channelData.values.push(0);
                    }
                }
            }
        }
        else if (length < this.point.length) {
            this.point.resize(length);
            this.edgeVisible.length = length;
            this.pointIndex.length = length;
            if (this.normal)
                this.normal.resize(length);
            if (this.param)
                this.param.resize(length);
            if (this.color)
                this.color.length = length;
            if (this.auxData) {
                for (const channel of this.auxData.channels) {
                    for (const channelData of channel.data) {
                        channelData.values.length = length * channel.entriesPerValue;
                    }
                }
            }
        }
    }
    /** Return the range of the point array (optionally transformed). */
    range(result, transform) {
        const range = Range3d.createNull(result);
        this.extendRange(range, transform);
        return range;
    }
    /** Extend `range` with coordinates from this mesh, optionally transformed. */
    extendRange(range, transform) {
        range.extendArray(this.point, transform);
    }
    /**
     * Apply a transform to the mesh data.
     * * Transform the data as follows:
     *   * apply `transform` to points.
     *   * apply inverse transpose of `transform` to normals and renormalize. This preserves normals perpendicular
     * to transformed facets, and keeps them pointing outward, e.g, if the mesh is closed. If the transform is not
     * invertible or a normal has zero length, the normal(s) are left unchanged, and this error is silently ignored.
     *   * apply `transform` to auxData.
     *   * scale faceData distances by the cube root of the absolute value of the determinant of `transform.matrix`.
     * * Note that if the transform is a mirror, this method does NOT reverse index order. This is the caller's
     * responsibility. This base class is just a data carrier: PolyfaceData does not know if the index order has
     * special meaning.
    * * Note that this method always returns true. If transforming normals fails (due to singular matrix or zero
     * normal), the original normal(s) are left unchanged.
     */
    tryTransformInPlace(transform) {
        this.point.multiplyTransformInPlace(transform);
        if (this.normal && !transform.matrix.isIdentity)
            this.normal.multiplyAndRenormalizeMatrix3dInverseTransposeInPlace(transform.matrix);
        if (this.face.length > 0) {
            const distScale = Math.cbrt(Math.abs(transform.matrix.determinant()));
            for (const faceData of this.face)
                faceData.scaleDistances(distScale);
        }
        if (this.auxData)
            this.auxData.tryTransformInPlace(transform);
        return true;
    }
    /**
     * Compress the instance by equating duplicate data.
     * * Search for duplicates within vertices, normals, params, and colors.
     * * Compress each data array.
     * * Revise all indexing for the relocated data.
     * * [[PolyfaceAuxData]] is compressed if and only if exactly one [[AuxChannelData]] is present.
     * @param tolerance (optional) tolerance for clustering mesh vertices only. Default value, and the tolerance used to cluster all other data, is [[Geometry.smallMetricDistance]].
     */
    compress(tolerance = Geometry.smallMetricDistance) {
        // more info can be found at geometry/internaldocs/Polyface.md
        const packedPoints = ClusterableArray.clusterGrowablePoint3dArray(this.point, tolerance);
        this.point = packedPoints.growablePackedPoints;
        packedPoints.updateIndices(this.pointIndex);
        // for now, normals, params, and colors use the default tolerance for clustering
        if (this.normalIndex && this.normal) {
            const packedNormals = ClusterableArray.clusterGrowablePoint3dArray(this.normal);
            this.normal = packedNormals.growablePackedPoints;
            packedNormals.updateIndices(this.normalIndex);
        }
        if (this.paramIndex && this.param) {
            const packedParams = ClusterableArray.clusterGrowablePoint2dArray(this.param);
            this.param = packedParams.growablePackedPoints;
            packedParams.updateIndices(this.paramIndex);
        }
        if (this.colorIndex && this.color) {
            const packedColors = ClusterableArray.clusterNumberArray(this.color);
            this.color = packedColors.packedNumbers;
            packedColors.updateIndices(this.colorIndex);
        }
        if (this.auxData && this.auxData.channels.length === 1 && this.auxData.channels[0].data.length === 1) {
            const dataSize = this.auxData.channels[0].entriesPerValue;
            if (1 === dataSize) {
                const packedData = ClusterableArray.clusterNumberArray(this.auxData.channels[0].data[0].values);
                this.auxData.channels[0].data[0].values = packedData.packedNumbers;
                packedData.updateIndices(this.auxData.indices);
            }
            else if (3 === dataSize) {
                const blockedData = GrowableXYZArray.create(this.auxData.channels[0].data[0].values);
                const packedData = ClusterableArray.clusterGrowablePoint3dArray(blockedData);
                this.auxData.channels[0].data[0].values = NumberArray.create(packedData.growablePackedPoints.float64Data());
                packedData.updateIndices(this.auxData.indices);
            }
        }
    }
    /**
     * Test if `facetStartIndex` is (minimally) valid.
     * * Length must be nonzero (recall that for "no facets", the `facetStartIndex` array still must contain a 0).
     * * Each entry must be strictly smaller than the one that follows.
     * @param facetStartIndex start indices of all facets. Facet k starts at facetStartIndex[k] up to (but not including)
     * `facetStartIndex[k + 1]`
     */
    static isValidFacetStartIndexArray(facetStartIndex) {
        // facetStartIndex for empty facets has a single entry "0" -- empty array is not allowed
        if (facetStartIndex.length === 0)
            return false;
        for (let i = 0; i + 1 < facetStartIndex.length; i++)
            if (facetStartIndex[i] >= facetStartIndex[i + 1])
                return false;
        return true;
    }
    /**
     * Reverse the indices for the specified facets in the given index array.
     * * Parameterized over type T so non-number data (e.g., boolean visibility flags) can be reversed.
     * @param facetStartIndex start indices of *consecutive* facets to be reversed, e.g., an IndexedPolyface's _facetStart
     * array. See the non-static [[reverseIndices]].
     * @param indices the index array, e.g., pointIndex, normalIndex, etc.
     * @param preserveStart `true` to preserve the start index of each facet (e.g., facet [1,2,3,4] becomes [1,4,3,2]);
     * `false` to reverse all indices (e.g., facet [1,2,3,4] becomes [4,3,2,1]).
     */
    static reverseIndices(facetStartIndex, indices, preserveStart) {
        if (!indices || indices.length === 0)
            return true; // empty case
        if (indices.length > 0) {
            if (facetStartIndex[facetStartIndex.length - 1] === indices.length) {
                for (let i = 0; i + 1 < facetStartIndex.length; i++) {
                    let index0 = facetStartIndex[i];
                    let index1 = facetStartIndex[i + 1];
                    if (preserveStart) { // leave "index0" as is so reversed facet starts at same vertex
                        while (index1 > index0 + 2) {
                            index1--;
                            index0++;
                            const a = indices[index0];
                            indices[index0] = indices[index1];
                            indices[index1] = a;
                        }
                    }
                    else { // reverse all
                        while (index1 > index0 + 1) {
                            index1--;
                            const a = indices[index0];
                            indices[index0] = indices[index1];
                            indices[index1] = a;
                            index0++;
                        }
                    }
                }
                return true;
            }
        }
        return false;
    }
    /**
     * Reverse the indices for the specified facet in the specified index array.
     * * Parameterized over type T so non-number data (e.g., boolean visibility flags) can be reversed.
     * @param facetIndex index of the facet to reverse. The entries of `indices` to be reversed are found at
     * `facetStartIndex[facetIndex] <= i < facetStartIndex[facetIndex + 1]`.
     * @param facetStartIndex start indices of *consecutive* facets, e.g., an IndexedPolyface's _facetStart array.
     * See [[reverseIndices]].
     * @param indices the index array, e.g., pointIndex, normalIndex, etc.
     * @param preserveStart `true` to preserve the start index of each facet (e.g., facet [1,2,3,4] becomes [1,4,3,2]);
     * `false` to reverse all indices (e.g., facet [1,2,3,4] becomes [4,3,2,1]).
     */
    static reverseIndicesSingleFacet(facetIndex, facetStartIndex, indices, preserveStart) {
        if (!indices || indices.length === 0)
            return true; // empty case
        if (indices.length > 0) {
            if (facetStartIndex[facetStartIndex.length - 1] === indices.length
                && facetIndex >= 0 && facetIndex + 1 < facetStartIndex.length) {
                let index0 = facetStartIndex[facetIndex];
                let index1 = facetStartIndex[facetIndex + 1];
                if (preserveStart) { // leave "index0" as is so reversed facet starts at same vertex
                    while (index1 > index0 + 2) {
                        index1--;
                        index0++;
                        const a = indices[index0];
                        indices[index0] = indices[index1];
                        indices[index1] = a;
                    }
                }
                else { // reverse all
                    while (index1 > index0 + 1) {
                        index1--;
                        const a = indices[index0];
                        indices[index0] = indices[index1];
                        indices[index1] = a;
                        index0++;
                    }
                }
                return true;
            }
        }
        return false;
    }
    /**
     * Reverse the indices for the specified facets in the index arrays (pointIndex, normalIndex, paramIndex, colorIndex,
     * and edgeVisible).
     * @param facetStartIndex start indices of *consecutive* facets to be reversed.
     * * Consecutive indices in this array define where a given facet is represented in each of the parallel index arrays.
     * * The indices for facet k are `facetStartIndex[k]` up to (but not including) `facetStartIndex[k + 1]`.
     * * This implies `facetStartIndex[k + 1]` is both the upper limit of facet k's indices, and the start index of facet k+1.
     * * For example, passing an IndexedPolyface's _facetStart array into this method reverses every facet.
    */
    reverseIndices(facetStartIndex) {
        if (facetStartIndex && PolyfaceData.isValidFacetStartIndexArray(facetStartIndex)) {
            PolyfaceData.reverseIndices(facetStartIndex, this.pointIndex, true);
            if (this.normalIndex !== this.pointIndex)
                PolyfaceData.reverseIndices(facetStartIndex, this.normalIndex, true);
            if (this.paramIndex !== this.pointIndex)
                PolyfaceData.reverseIndices(facetStartIndex, this.paramIndex, true);
            if (this.colorIndex !== this.pointIndex)
                PolyfaceData.reverseIndices(facetStartIndex, this.colorIndex, true);
            PolyfaceData.reverseIndices(facetStartIndex, this.edgeVisible, false);
            // TODO: reverse auxData.indices, edgeMateIndex
        }
    }
    /**
     * Reverse the indices for the specified facet in the index arrays (pointIndex, normalIndex, paramIndex, colorIndex,
     * and edgeVisible).
     * @param facetIndex index of the facet to reverse. The entries of each index array to be reversed are found at
     * `facetStartIndex[facetIndex] <= i < facetStartIndex[facetIndex + 1]`.
     * @param facetStartIndex start indices of *consecutive* facets, e.g., an IndexedPolyface's _facetStart array.
     * See [[reverseIndices]].
     */
    reverseIndicesSingleFacet(facetIndex, facetStartIndex) {
        PolyfaceData.reverseIndicesSingleFacet(facetIndex, facetStartIndex, this.pointIndex, true);
        if (this.normalIndex !== this.pointIndex)
            PolyfaceData.reverseIndicesSingleFacet(facetIndex, facetStartIndex, this.normalIndex, true);
        if (this.paramIndex !== this.pointIndex)
            PolyfaceData.reverseIndicesSingleFacet(facetIndex, facetStartIndex, this.paramIndex, true);
        if (this.colorIndex !== this.pointIndex)
            PolyfaceData.reverseIndicesSingleFacet(facetIndex, facetStartIndex, this.colorIndex, true);
        PolyfaceData.reverseIndicesSingleFacet(facetIndex, facetStartIndex, this.edgeVisible, false);
        // TODO: reverse auxData.indices, edgeMateIndex
    }
    /** Scale all the normals by -1. */
    reverseNormals() {
        if (this.normal)
            this.normal.scaleInPlace(-1.0);
    }
}
//# sourceMappingURL=PolyfaceData.js.map