"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.PolyfaceAuxData = exports.AuxChannel = exports.AuxChannelData = exports.AuxChannelDataType = void 0;
const Point3dVector3d_1 = require("../geometry3d/Point3dVector3d");
const PointHelpers_1 = require("../geometry3d/PointHelpers");
// import { Geometry } from "./Geometry";
const Range_1 = require("../geometry3d/Range");
/** The types of data that can be represented by an [[AuxChannelData]]. Each type of data contributes differently to the
 * animation applied by an [AnalysisStyle]($common) and responds differently when the host [[PolyfaceAuxData]] is transformed.
 * @public
 */
var AuxChannelDataType;
(function (AuxChannelDataType) {
    /** General-purpose scalar values like stress, temperature, etc., used to recolor the [[Polyface]]'s vertices.
     * When the host Polyface is transformed, scalar values remain unmodified.
     */
    AuxChannelDataType[AuxChannelDataType["Scalar"] = 0] = "Scalar";
    /** Distances in meters used to recolor the [[Polyface]]'s vertices.
     * When the host [[Polyface]] is transformed the [[Transform]]'s scale is applied to the distances.
     */
    AuxChannelDataType[AuxChannelDataType["Distance"] = 1] = "Distance";
    /** (X, Y, Z) displacement vectors added to the [[Polyface]]'s vertex positions resulting in deformation of the mesh.
     * When the host Polyface is transformed the displacements are rotated and scaled accordingly.
     */
    AuxChannelDataType[AuxChannelDataType["Vector"] = 2] = "Vector";
    /** (X, Y, Z) unit normal vectors that replace the host [[Polyface]]'s own normals.
     * When the Polyface is transformed the normals are rotated and renormalized accordingly.
     */
    AuxChannelDataType[AuxChannelDataType["Normal"] = 3] = "Normal";
})(AuxChannelDataType || (exports.AuxChannelDataType = AuxChannelDataType = {}));
/**  Represents the [[AuxChannel]] data at a single input value.
 * @public
*/
class AuxChannelData {
    /** The input value for this data. */
    input;
    /** The vertex values for this data. A single value per vertex for scalar and distance types and 3 values (x,y,z) for normal or vector channels. */
    values;
    /** Constructor. If `values` is a number array, it is captured.  */
    constructor(input, values) {
        this.input = input;
        if (values instanceof Float64Array) {
            this.values = [];
            for (const v of values)
                this.values.push(v);
        }
        else
            this.values = values;
    }
    /** Copy blocks of size `blockSize` from (blocked index) `thisIndex` in this AuxChannelData to (blockIndex) `otherIndex` of `other` */
    copyValues(other, thisIndex, otherIndex, blockSize) {
        for (let i = 0; i < blockSize; i++)
            this.values[thisIndex * blockSize + i] = other.values[otherIndex * blockSize + i];
    }
    /** return a deep copy */
    clone() {
        return new AuxChannelData(this.input, this.values.slice());
    }
    /** toleranced comparison of the `input` and `value` fields.
     * * Default tolerance is 1.0e-8
     */
    isAlmostEqual(other, tol) {
        const tolerance = tol ? tol : 1.0E-8;
        return Math.abs(this.input - other.input) < tolerance && PointHelpers_1.NumberArray.isAlmostEqual(this.values, other.values, tolerance);
    }
}
exports.AuxChannelData = AuxChannelData;
/**  Represents a single [[PolyfaceAuxData]] channel.
 * @public
*/
class AuxChannel {
    /** An array of [[AuxChannelData]] that represents the vertex data at one or more input values. */
    data;
    /** The type of data stored in this channel. */
    dataType;
    /** The channel name. This is used to present the [[AuxChannel]] to the user and also to select the [[AuxChannel]] for display from AnalysisStyle */
    name;
    /** The input name. */
    inputName;
    /** Constructor with CAPTURED inputs. */
    constructor(data, dataType, name, inputName) {
        this.data = data;
        this.dataType = dataType;
        this.name = name;
        this.inputName = inputName;
    }
    /** Return a deep copy. */
    clone() {
        const clonedData = [];
        for (const data of this.data)
            clonedData.push(data.clone());
        return new AuxChannel(clonedData, this.dataType, this.name, this.inputName);
    }
    /** Toleranced comparison of contents. */
    isAlmostEqual(other, tol) {
        if (this.dataType !== other.dataType ||
            this.name !== other.name ||
            this.inputName !== other.inputName ||
            this.data.length !== other.data.length)
            return false;
        for (let i = 0; i < this.data.length; i++)
            if (!this.data[i].isAlmostEqual(other.data[i], tol))
                return false;
        return true;
    }
    /** True if the data type is 1-dimensional. */
    static isScalar(dataType) {
        return dataType === AuxChannelDataType.Distance || dataType === AuxChannelDataType.Scalar;
    }
    /** True if the data stored in this AuxChannel is 1-dimensional. */
    get isScalar() {
        return AuxChannel.isScalar(this.dataType);
    }
    /** The dimension (1D or 3D) of each datum of an AuxChannel of the given type. */
    static entriesPerValue(dataType) {
        return this.isScalar(dataType) ? 1 : 3;
    }
    /** The dimension (1D or 3D) of each datum in the data arrays of this AuxChannel. */
    get entriesPerValue() {
        return AuxChannel.entriesPerValue(this.dataType);
    }
    /** The number of data stored in each data array of this AuxChannel, equal to the length of the array divided by `entriesPerValue`. */
    get valueCount() {
        return 0 === this.data.length ? 0 : this.data[0].values.length / this.entriesPerValue;
    }
    /** The minimum and maximum values in `data.values`, or `undefined` if [[isScalar]] is false. */
    get scalarRange() {
        if (!this.isScalar)
            return undefined;
        const range = Range_1.Range1d.createNull();
        for (const data of this.data)
            range.extendArray(data.values);
        return range;
    }
    /** Compute the range of this channel's displacement values, if [[dataType]] is [[AuxChannelDataType.Vector]].
     * @param scale Scale by which to multiply each displacement.
     * @param result Preallocated object in which to store result.
     * @returns The range encompassing all this channel's displacements scaled by `scale`; or a null range if this channel does not contain displacements.
     */
    computeDisplacementRange(scale = 1, result) {
        result = Range_1.Range3d.createNull(result);
        if (AuxChannelDataType.Vector === this.dataType) {
            for (const data of this.data) {
                const v = data.values;
                for (let i = 0; i < v.length; i += 3)
                    result.extendXYZ(v[i] * scale, v[i + 1] * scale, v[i + 2] * scale);
            }
        }
        return result;
    }
}
exports.AuxChannel = AuxChannel;
/**
 * The `PolyfaceAuxData` structure contains one or more analytical data channels for each vertex of a [[Polyface]], allowing the polyface to be styled
 * using an [AnalysisStyle]($common).
 * Typically a polyface will contain only vertex data required for its basic display: vertex position, normal, texture parameter, color.
 * `PolyfaceAuxData` provides supplemental per-vertex data that is generally computed in an analysis program or other external data source.
 * This supplemental data can be either 1D (e.g., height, override color) or 3D (e.g., displacement vector, override normal); see [[AuxChannel.entriesPerValue]], [[AuxChannel.dataType]].
 * All data channels are indexed by the same indices, which must have the same length and structure as the other Polyface indices.
 * This means that if a facet's face loop is found at index range [i0,i1] in the Polyface vertex index array, then the same index range [i0,i1]
 * locates the data for this facet in all the other Polyface index arrays, including the `PolyfaceAuxData` indices.
 * @see [[PolyfaceData.auxData]] to associate auxiliary data with a polyface.
 * @public
 */
class PolyfaceAuxData {
    /** Array with one or more channels of auxiliary data for the associated polyface. */
    channels;
    /** The indices (shared by all data in all channels) mapping the data to the mesh facets. */
    indices;
    /** Constructor with CAPTURED inputs. */
    constructor(channels, indices) {
        this.channels = channels;
        this.indices = indices;
    }
    /** Return a deep copy. */
    clone() {
        const clonedChannels = this.channels.map((x) => x.clone());
        return new PolyfaceAuxData(clonedChannels, this.indices.slice());
    }
    /** Returns true if `this` is equivalent to `other` within `tolerance`.
     * The indices are compared for exact equality. The data in the channels are compared using `tolerance`, which defaults to 1.0e-8.
     */
    isAlmostEqual(other, tolerance) {
        if (!PointHelpers_1.NumberArray.isExactEqual(this.indices, other.indices) || this.channels.length !== other.channels.length)
            return false;
        for (let i = 0; i < this.channels.length; i++)
            if (!this.channels[i].isAlmostEqual(other.channels[i], tolerance))
                return false;
        return true;
    }
    /** Returns true if both `left` and `right` are undefined, or both are defined and equivalent within `tolerance` (default: 1.0e-8). */
    static isAlmostEqual(left, right, tol) {
        if (left === right) // This catches double undefined !!!
            return true;
        if (left && right)
            return left.isAlmostEqual(right, tol);
        return false;
    }
    /** Create a PolyfaceAuxData for use by a [[PolyfaceVisitor]].  */
    createForVisitor() {
        const visitorChannels = [];
        for (const parentChannel of this.channels) {
            const visitorChannelData = [];
            for (const parentChannelData of parentChannel.data)
                visitorChannelData.push(new AuxChannelData(parentChannelData.input, []));
            visitorChannels.push(new AuxChannel(visitorChannelData, parentChannel.dataType, parentChannel.name, parentChannel.inputName));
        }
        return new PolyfaceAuxData(visitorChannels, []);
    }
    /** Apply `transform` to the data in each channel.
     * @see [[AuxChannelDataType]] for details regarding how each data type is affected by the transform.
     * @note This method may fail if a channel of [[AuxChannelDataType.Normal]] exists and `transform.matrix` is non-invertible.
     * @returns true if the channels were all successfully transformed.
     */
    tryTransformInPlace(transform) {
        let inverseRot;
        const rot = transform.matrix;
        const det = rot.determinant();
        const scale = Math.pow(Math.abs(det), 1 / 3) * (det >= 0 ? 1 : -1);
        for (const channel of this.channels) {
            for (const data of channel.data) {
                switch (channel.dataType) {
                    case AuxChannelDataType.Scalar:
                        continue;
                    case AuxChannelDataType.Distance: {
                        for (let i = 0; i < data.values.length; i++)
                            data.values[i] *= scale;
                        break;
                    }
                    case AuxChannelDataType.Normal: {
                        inverseRot = inverseRot ?? rot.inverse();
                        if (!inverseRot)
                            return false;
                        transformPoints(data.values, (point) => {
                            inverseRot.multiplyTransposeVectorInPlace(point);
                            const dot = point.magnitudeSquared();
                            const tol = 1.0e-15; // cf. GrowableXYZArray.multiplyAndRenormalizeMatrix3dInverseTransposeInPlace
                            if (dot > tol && Math.abs(dot - 1.0) > tol) { // only renormalize if magnitude is not near 0 or 1
                                const mag = 1.0 / Math.sqrt(dot);
                                point.scaleInPlace(mag);
                            }
                        });
                        break;
                    }
                    case AuxChannelDataType.Vector: {
                        transformPoints(data.values, (point) => rot.multiplyVectorInPlace(point));
                        break;
                    }
                }
            }
        }
        return true;
    }
}
exports.PolyfaceAuxData = PolyfaceAuxData;
function transformPoints(coords, transform) {
    const point = new Point3dVector3d_1.Point3d();
    for (let i = 0; i < coords.length; i += 3) {
        point.set(coords[i], coords[i + 1], coords[i + 2]);
        transform(point);
        coords[i] = point.x;
        coords[i + 1] = point.y;
        coords[i + 2] = point.z;
    }
}
//# sourceMappingURL=AuxData.js.map