"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 Polyface
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.BuildAverageNormalsContext = void 0;
const Point3dVector3d_1 = require("../../geometry3d/Point3dVector3d");
const PolygonOps_1 = require("../../geometry3d/PolygonOps");
const Geometry_1 = require("../../Geometry");
const GrowableXYZArray_1 = require("../../geometry3d/GrowableXYZArray");
/**
 * Normal vector with area (or other numeric) and source index
 * @internal
 */
class IndexedAreaAndNormal {
    constructor(index, area, normal) {
        this.index = index;
        this.area = area;
        this.normal = normal;
    }
    index;
    area;
    normal;
    addWeightedNormal(weight, normal) {
        this.area += weight;
        this.normal.addScaledInPlace(normal, weight);
    }
    divideNormalByArea() {
        this.normal.scaleInPlace(1.0 / this.area);
    }
}
/**
 * index data for a single sector of some facet.
 * @internal
 */
class SectorData {
    constructor(facetData, sectorIndex, vertexIndex) {
        this.facetData = facetData;
        this.sectorClusterData = undefined;
        this.sectorIndex = sectorIndex;
        this.vertexIndex = vertexIndex;
    }
    facetData;
    sectorIndex;
    vertexIndex;
    sectorClusterData;
    static cbSectorSort(left, right) { return left.sectorIndex - right.sectorIndex; }
    static cbVertexSort(left, right) { return left.vertexIndex - right.vertexIndex; }
    static pushToArray(data, facetData, sectorIndex, vertexIndex) {
        data.push(new SectorData(facetData, sectorIndex, vertexIndex));
    }
}
/**
 * Helper context for normal averaging.
 * All methods are static.
 * @internal
 */
class BuildAverageNormalsContext {
    /**
     * * At each vertex of the mesh
     *   * Find clusters of almost parallel normals
     *   * Compute simple average of those normals
     *   * Index to the averages
     * * For typical meshes, this correctly clusters adjacent normals.
     * * One cam imagine a vertex with multiple "smooth cone-like" sets of incident facets such that averaging occurs among two nonadjacent cones.  But this does not seem to be a problem in practice.
     * @param polyface polyface to update.
     * @param toleranceAngle averaging is done between normals up to this angle.
     */
    static buildFastAverageNormals(polyface, toleranceAngle) {
        // We ASSUME that the visitor order matches index order in polyface.data .....
        const visitor = polyface.createVisitor(0);
        const defaultNormal = Point3dVector3d_1.Vector3d.create(0, 0, 1);
        const smallArea = Geometry_1.Geometry.smallMetricDistanceSquared; // I DO NOT LIKE THIS TOLERANCE
        const sectors = [];
        let facetIndex = 0;
        let sectorIndex = 0;
        // create one IndexedAreaNormal structure for each facet.
        // At each sector of each face, notate (a) IndexedAreaNormal of the facet, (b) the sector index, (c) the point index.
        while (visitor.moveToNextFacet()) {
            let facetNormal = PolygonOps_1.PolygonOps.areaNormalGo(visitor.point);
            let area = 0.0;
            if (!facetNormal) {
                facetNormal = defaultNormal.clone();
                area = 0.0;
            }
            else {
                area = facetNormal.magnitude();
                if (area < smallArea) {
                    facetNormal.setFromVector3d(defaultNormal);
                    area = 0.0;
                }
                else {
                    facetNormal.scaleInPlace(1.0 / area);
                }
            }
            const facetData = new IndexedAreaAndNormal(facetIndex++, area, facetNormal);
            for (let i = 0; i < visitor.pointCount; i++) {
                SectorData.pushToArray(sectors, facetData, sectorIndex++, visitor.clientPointIndex(i));
            }
        }
        // Sort by the vertex index so all the sectors around each vertex are clustered . .
        sectors.sort((left, right) => SectorData.cbVertexSort(left, right));
        // Walk the sectors around each vertex .  ..
        // For each unassigned sector
        //     walk to further sectors of the same vertex with near-parallel normals, accumulating average normal.
        //     notate all sectors in the cluster with the averaged-normal structure.
        const clusters = [];
        let toleranceRadians = toleranceAngle.radians;
        if (toleranceRadians < 0.0001)
            toleranceRadians = 0.0001;
        let clusterIndex = 0;
        for (let baseSectorIndex = 0; baseSectorIndex < sectors.length; baseSectorIndex++) {
            const baseData = sectors[baseSectorIndex];
            const vertexIndex = baseData.vertexIndex;
            const baseFacetData = baseData.facetData;
            if (baseData.sectorClusterData === undefined) {
                const clusterNormal = new IndexedAreaAndNormal(clusterIndex++, 0.0, Point3dVector3d_1.Vector3d.createZero());
                clusters.push(clusterNormal);
                // Accumulate with equal weights . . .
                clusterNormal.addWeightedNormal(1.0, baseData.facetData.normal);
                for (let candidateSectorIndex = baseSectorIndex; candidateSectorIndex < sectors.length; candidateSectorIndex++) {
                    const candidateSector = sectors[candidateSectorIndex];
                    if (candidateSector.vertexIndex !== vertexIndex)
                        break;
                    if (candidateSector.facetData.normal.angleTo(baseFacetData.normal).radians > toleranceRadians)
                        continue;
                    if (candidateSector.sectorClusterData === undefined) {
                        clusterNormal.addWeightedNormal(1.0, candidateSector.facetData.normal);
                        candidateSector.sectorClusterData = clusterNormal;
                    }
                }
            }
        }
        // Resort by original sector index.
        sectors.sort((left, right) => SectorData.cbSectorSort(left, right));
        // normalize the sums and emplace in the facets  . . .
        polyface.data.normalIndex = [];
        polyface.data.normal = new GrowableXYZArray_1.GrowableXYZArray(sectors.length);
        for (const cluster of clusters) {
            cluster.divideNormalByArea();
            cluster.index = polyface.data.normal.length;
            polyface.data.normal.push(cluster.normal);
        }
        // emplace the indices
        for (const sector of sectors) {
            polyface.data.normalIndex.push(sector.sectorClusterData.index);
        }
    }
    /**
     * Set up indexed normals with one normal in the plane of each facet of the mesh.
     * @param polyface mesh to modify
     */
    static buildPerFaceNormals(polyface) {
        const visitor = polyface.createVisitor(0);
        const facetNormal = Point3dVector3d_1.Vector3d.create(0, 0, 1);
        const defaultNormal = Point3dVector3d_1.Vector3d.create(0, 0, 1);
        // polyface.data.clearNormals();
        const newNormals = new GrowableXYZArray_1.GrowableXYZArray(polyface.faceCount);
        const newIndices = [];
        while (visitor.moveToNextFacet()) {
            const thisNormalIndex = newNormals.length;
            if (PolygonOps_1.PolygonOps.unitNormal(visitor.point, facetNormal))
                newNormals.push(facetNormal);
            else
                newNormals.push(defaultNormal);
            for (let i = 0; i < visitor.pointCount; i++)
                newIndices.push(thisNormalIndex);
        }
        polyface.data.normalIndex = newIndices;
        polyface.data.normal = newNormals;
    }
}
exports.BuildAverageNormalsContext = BuildAverageNormalsContext;
//# sourceMappingURL=BuildAverageNormalsContext.js.map