/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Rendering
 */
import { Matrix3d, Point2d, Point3d, Transform, Vector3d } from "@itwin/core-geometry";
import { compareBooleans, compareBooleansOrUndefined, compareNumbers, compareNumbersOrUndefined, comparePossiblyUndefined } from "@itwin/core-bentley";
function compareNormalMapParams(lhs, rhs) {
    return comparePossiblyUndefined((lh, rh) => lh.compare(rh), lhs.normalMap, rhs.normalMap)
        || compareBooleansOrUndefined(lhs.greenUp, rhs.greenUp) || compareNumbersOrUndefined(lhs.scale, rhs.scale)
        || compareBooleansOrUndefined(lhs.useConstantLod, rhs.useConstantLod);
}
/** Describes how to map a [[RenderTexture]]'s image onto a surface as part of a [[RenderMaterial]].
 * @public
 */
export class TextureMapping {
    constructor(tx, params) {
        this.texture = tx;
        this.params = params;
    }
    /** @internal */
    computeUVParams(visitor, transformToImodel) {
        return this.params.computeUVParams(visitor, transformToImodel);
    }
    /** An [OrderedComparator]($bentley) that compares this mapping against `other`. */
    compare(other) {
        if (this === other) {
            return 0;
        }
        return this.texture.compare(other.texture) || this.params.compare(other.params)
            || comparePossiblyUndefined((lh, rh) => compareNormalMapParams(lh, rh), this.normalMapParams, other.normalMapParams);
    }
}
/** @public */
(function (TextureMapping) {
    /** Enumerates the possible texture mapping modes. */
    let Mode;
    (function (Mode) {
        Mode[Mode["None"] = -1] = "None";
        Mode[Mode["Parametric"] = 0] = "Parametric";
        Mode[Mode["ElevationDrape"] = 1] = "ElevationDrape";
        Mode[Mode["Planar"] = 2] = "Planar";
        /** @internal */
        Mode[Mode["DirectionalDrape"] = 3] = "DirectionalDrape";
        /** @internal */
        Mode[Mode["Cubic"] = 4] = "Cubic";
        /** @internal */
        Mode[Mode["Spherical"] = 5] = "Spherical";
        /** @internal */
        Mode[Mode["Cylindrical"] = 6] = "Cylindrical";
        /** @internal */
        Mode[Mode["Solid"] = 7] = "Solid";
        /** @internal Only valid for lights */
        Mode[Mode["FrontProject"] = 8] = "FrontProject";
    })(Mode = TextureMapping.Mode || (TextureMapping.Mode = {}));
    /** A 2x3 matrix for mapping a texture image to a surface. */
    class Trans2x3 {
        /** Construct from the two rows of the matrix:
         * ```
         *  | m00 m01 originX |
         *  | m10 m11 originY |
         * ```
         * Producing the [Transform]($core-geometry):
         * ```
         *  | m00 m01 0 originX |
         *  | m10 m11 0 originY |
         *  | 0   0   1 0       |
         * ```
         */
        constructor(m00 = 1, m01 = 0, originX = 0, m10 = 0, m11 = 1, originY = 0) {
            const origin = new Point3d(originX, originY, 0);
            const matrix = Matrix3d.createRowValues(m00, m01, 0, m10, m11, 0, 0, 0, 1);
            this.transform = Transform.createRefs(origin, matrix);
        }
        /** An [OrderedComparator]($bentley) that compares this Trans2x3 against `other`. */
        compare(other) {
            if (this === other) {
                return 0;
            }
            const originDiff = compareNumbers(this.transform.origin.x, other.transform.origin.x) || compareNumbers(this.transform.origin.y, other.transform.origin.y);
            if (originDiff !== 0) {
                return originDiff;
            }
            for (const i of [0, 1, 3, 4]) {
                const matDiff = compareNumbers(this.transform.matrix.coffs[i], other.transform.matrix.coffs[i]);
                if (matDiff !== 0) {
                    return matDiff;
                }
            }
            return 0;
        }
    }
    /** An immutable 2x3 identity matrix. */
    Trans2x3.identity = new Trans2x3();
    TextureMapping.Trans2x3 = Trans2x3;
    function compareConstantLodParams(lhs, rhs) {
        return compareNumbers(lhs.repetitions, rhs.repetitions) || compareNumbers(lhs.offset.x, rhs.offset.x) || compareNumbers(lhs.offset.y, rhs.offset.y)
            || compareNumbers(lhs.minDistClamp, rhs.minDistClamp) || compareNumbers(lhs.maxDistClamp, rhs.maxDistClamp);
    }
    /** Parameters describing how a [[RenderTexture]]'s image is mapped to a surface. */
    class Params {
        constructor(props) {
            this.textureMatrix = props?.textureMat2x3 ?? Trans2x3.identity;
            this.weight = props?.textureWeight ?? 1;
            this.mode = props?.mapMode ?? Mode.Parametric;
            this.worldMapping = props?.worldMapping ?? false;
            this.useConstantLod = props?.useConstantLod ?? false;
            this.constantLodParams = {
                repetitions: props?.constantLodProps?.repetitions ?? 1,
                offset: props?.constantLodProps?.offset ?? { x: 0, y: 0 },
                minDistClamp: props?.constantLodProps?.minDistClamp ?? 1,
                maxDistClamp: props?.constantLodProps?.maxDistClamp ?? 4096 * 1024 * 1024,
            };
        }
        /** An [OrderedComparator]($bentley) that compares these Params against `other`. */
        compare(other) {
            if (this === other) {
                return 0;
            }
            return compareNumbers(this.weight, other.weight) || compareNumbers(this.mode, other.mode) || compareBooleans(this.worldMapping, other.worldMapping)
                || compareBooleans(this.useConstantLod, other.useConstantLod) || this.textureMatrix.compare(other.textureMatrix)
                || compareConstantLodParams(this.constantLodParams, other.constantLodParams);
        }
        /**
         * Generates UV parameters for textured surfaces. Returns undefined on failure.
         * @internal
         */
        computeUVParams(visitor, transformToImodel) {
            switch (this.mode) {
                default: // Fall through to parametric in default case
                case TextureMapping.Mode.Parametric: {
                    return this.computeParametricUVParams(visitor, this.textureMatrix.transform, !this.worldMapping);
                }
                case TextureMapping.Mode.Planar: {
                    const normalIndices = visitor.normalIndex;
                    if (!normalIndices)
                        return undefined;
                    // Ignore planar mode unless master or sub units for scaleMode and facet is planar
                    if (!this.worldMapping || (visitor.normalIndex !== undefined && (normalIndices[0] !== normalIndices[1] || normalIndices[0] !== normalIndices[2]))) {
                        return this.computeParametricUVParams(visitor, this.textureMatrix.transform, !this.worldMapping);
                    }
                    else {
                        return this.computePlanarUVParams(visitor, this.textureMatrix.transform);
                    }
                }
                case TextureMapping.Mode.ElevationDrape: {
                    return this.computeElevationDrapeUVParams(visitor, this.textureMatrix.transform, transformToImodel);
                }
            }
        }
        /** Computes UV parameters given a texture mapping mode of parametric. */
        computeParametricUVParams(visitor, uvTransform, isRelativeUnits) {
            const params = [];
            for (let i = 0; i < visitor.numEdgesThisFacet; i++) {
                let param = Point2d.create();
                if (isRelativeUnits || !visitor.tryGetDistanceParameter(i, param)) {
                    if (!visitor.tryGetNormalizedParameter(i, param)) {
                        // If mesh does not have facetFaceData, we still want to use the texture coordinates if they are present
                        param = visitor.getParam(i);
                    }
                }
                params.push(uvTransform.multiplyPoint2d(param));
            }
            return params;
        }
        /** Computes UV parameters given a texture mapping mode of planar. The result is stored in the Point2d array given. */
        computePlanarUVParams(visitor, uvTransform) {
            const params = [];
            const points = visitor.point;
            let normal;
            if (visitor.normal === undefined)
                normal = points.getPoint3dAtUncheckedPointIndex(0).crossProductToPoints(points.getPoint3dAtUncheckedPointIndex(1), points.getPoint3dAtUncheckedPointIndex(2));
            else
                normal = visitor.normal.getVector3dAtCheckedVectorIndex(0);
            if (!normal.normalize(normal))
                return undefined;
            // adjust U texture coordinate to be a continuous length starting at the
            // origin. V coordinate stays the same. This mode assumes Z is up vector
            // Flipping normal puts us in a planar coordinate system consistent with MicroStation's display system
            normal.scale(-1.0, normal);
            // pick the first vertex normal
            const sideVector = Vector3d.create(normal.y, -normal.x, 0.0);
            // if the magnitude of the normal is near zero, the real normal points
            // almost straighten up.. In this case, use Y as the up vector to match QV
            const magnitude = sideVector.magnitude();
            sideVector.normalize(sideVector); // won't remain undefined if failed due to following check..
            if (magnitude < 1e-3) {
                normal.set(0, 0, -1);
                sideVector.set(1, 0, 0);
            }
            const upVector = sideVector.crossProduct(normal).normalize();
            if (!upVector)
                return undefined;
            const numEdges = visitor.numEdgesThisFacet;
            for (let i = 0; i < numEdges; i++) {
                const vector = Vector3d.createFrom(points.getPoint3dAtUncheckedPointIndex(i));
                params.push(Point2d.create(vector.dotProduct(sideVector), vector.dotProduct(upVector)));
                uvTransform.multiplyPoint2d(params[i], params[i]);
            }
            return params;
        }
        /** Computes UV parameters given a texture mapping mode of elevation drape. The result is stored in the Point2d array given. */
        computeElevationDrapeUVParams(visitor, uvTransform, transformToIModel) {
            const params = [];
            const numEdges = visitor.numEdgesThisFacet;
            for (let i = 0; i < numEdges; i++) {
                const point = visitor.point.getPoint3dAtUncheckedPointIndex(i);
                if (transformToIModel !== undefined)
                    transformToIModel.multiplyPoint3d(point, point);
                params.push(Point2d.createFrom(point));
                uvTransform.multiplyPoint2d(params[i], params[i]);
            }
            return params;
        }
    }
    TextureMapping.Params = Params;
})(TextureMapping || (TextureMapping = {}));
Object.freeze(TextureMapping.Trans2x3.identity);
//# sourceMappingURL=TextureMapping.js.map