"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 Rendering
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.MeshBuilderPolyface = exports.MeshEdgeCreationOptions = exports.MeshBuilder = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const Primitives_1 = require("./Primitives");
const VertexKey_1 = require("./VertexKey");
const MeshPrimitives_1 = require("./MeshPrimitives");
/** @internal */
class MeshBuilder {
    get currentPolyface() { return this._currentPolyface; }
    get displayParams() { return this.mesh.displayParams; }
    set displayParams(params) { this.mesh.displayParams = params; }
    /** create reference for triangleSet on demand */
    get triangleSet() {
        if (undefined === this._triangleSet)
            this._triangleSet = new Primitives_1.TriangleSet();
        return this._triangleSet;
    }
    constructor(mesh, tolerance, areaTolerance, tileRange) {
        this.mesh = mesh;
        this.tolerance = tolerance;
        this.areaTolerance = areaTolerance;
        this.tileRange = tileRange;
        let vertexTolerance;
        if (mesh.points instanceof core_common_1.QPoint3dList) {
            const p0 = mesh.points.params.unquantize(0, 0, 0);
            const p1 = mesh.points.params.unquantize(1, 1, 1);
            vertexTolerance = p1.minus(p0, p0);
        }
        else {
            vertexTolerance = { x: tolerance, y: tolerance, z: tolerance };
        }
        this.vertexMap = new VertexKey_1.VertexMap(vertexTolerance);
    }
    /** create a new MeshBuilder */
    static create(props) {
        const mesh = MeshPrimitives_1.Mesh.create(props);
        const { tolerance, areaTolerance, range } = props;
        return new MeshBuilder(mesh, tolerance, areaTolerance, range);
    }
    /**
     * iterate through each point list of the strokes primitive and either load the point string or polyline into builder
     * @param strokes lists of stroke primitive point lists to iterate
     * @param isDisjoint if true add point string, else add polyline
     * @param fillColor
     */
    addStrokePointLists(strokes, isDisjoint, fillColor, feature) {
        for (const strokePoints of strokes) {
            if (isDisjoint)
                this.addPointString(strokePoints.points, fillColor, feature);
            else
                this.addPolyline(strokePoints.points, fillColor, feature);
        }
    }
    /**
     * add data from polyface into mesh builder
     * @param polyface the indexed polyface to iterate the facets of to load each facet's triangles' vertices
     * @param props the properties required for this operation
     */
    addFromPolyface(polyface, props, feature) {
        this.beginPolyface(polyface, props.edgeOptions);
        const visitor = polyface.createVisitor();
        while (visitor.moveToNextFacet()) {
            this.addFromPolyfaceVisitor(visitor, props, feature);
        }
        this.endPolyface();
    }
    /**
     * @param visitor the PolyfaceVisitor containing the face data to be added
     * @param props the properties required for this operation:
     */
    addFromPolyfaceVisitor(visitor, options, feature) {
        const { pointCount, normalCount, paramCount, requireNormals } = visitor;
        const { includeParams, mappedTexture } = options;
        const isDegenerate = requireNormals && normalCount < pointCount; // TFS#790263: Degenerate triangle - no normals.
        // a triangle must have at least 3 points
        if (pointCount < 3 || isDegenerate)
            return;
        const haveParam = includeParams && paramCount > 0;
        const triangleCount = pointCount - 2;
        (0, core_bentley_1.assert)(!includeParams || paramCount > 0);
        (0, core_bentley_1.assert)(!haveParam || undefined !== mappedTexture);
        // The face represented by this visitor should be convex (we request that in facet options) - so we do a simple fan triangulation.
        const polyfaceVisitorOptions = { ...options, triangleCount, haveParam };
        for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++) {
            const triangle = this.createTriangle(triangleIndex, visitor, polyfaceVisitorOptions, feature);
            if (undefined !== triangle)
                this.addTriangle(triangle);
        }
    }
    createTriangleVertices(triangleIndex, visitor, options, feature) {
        const { point, requireNormals } = visitor;
        const { fillColor, haveParam } = options;
        // If we do not have UVParams stored on the IndexedPolyface, compute them now
        let params;
        if (haveParam && options.mappedTexture) {
            (0, core_bentley_1.assert)(this.mesh.points.length === 0 || this.mesh.uvParams.length !== 0);
            const mappedTexture = options.mappedTexture;
            const transformToImodel = mappedTexture.params.textureMatrix.transform;
            if (transformToImodel)
                params = mappedTexture.computeUVParams(visitor, transformToImodel);
            (0, core_bentley_1.assert)(params !== undefined);
        }
        const vertices = [];
        for (let i = 0; i < 3; ++i) {
            const vertexIndex = 0 === i ? 0 : triangleIndex + i;
            const position = point.getPoint3dAtUncheckedPointIndex(vertexIndex);
            const normal = requireNormals ? core_common_1.OctEncodedNormal.fromVector(visitor.getNormal(vertexIndex)) : undefined;
            const uvParam = params ? params[vertexIndex] : undefined;
            vertices[i] = { position, fillColor, normal, uvParam, sourceIndex: vertexIndex, feature };
        }
        // Previously we would add all 3 vertices to our map, then detect degenerate triangles in AddTriangle().
        // This led to unused vertex data, and caused mismatch in # of vertices when recreating the MeshBuilder from the data in the tile cache.
        // Detect beforehand instead.
        if (this.vertexMap.arePositionsAlmostEqual(vertices[0], vertices[1])
            || this.vertexMap.arePositionsAlmostEqual(vertices[0], vertices[2])
            || this.vertexMap.arePositionsAlmostEqual(vertices[1], vertices[2]))
            return undefined;
        return vertices;
    }
    createTriangle(triangleIndex, visitor, options, feature) {
        // generate vertex key properties for each of the three sides of the triangle
        const vertices = this.createTriangleVertices(triangleIndex, visitor, options, feature);
        // avoid creating degenerate triangles
        if (undefined === vertices)
            return undefined;
        const { edgeVisible } = visitor;
        const triangle = new Primitives_1.Triangle();
        triangle.setEdgeVisibility(0 === triangleIndex ? edgeVisible[0] : false, edgeVisible[triangleIndex + 1], triangleIndex === options.triangleCount - 1 ? edgeVisible[triangleIndex + 2] : false);
        // set each triangle index to the index associated with the vertex key location in the vertex map
        vertices.forEach((vertexProps, i) => {
            let vertexKeyIndex;
            if (visitor.auxData) {
                // No deduplication with auxData (for now...)
                vertexKeyIndex = this.mesh.addVertex(vertexProps);
                this.mesh.addAuxChannels(visitor.auxData.channels, vertexProps.sourceIndex);
            }
            else {
                vertexKeyIndex = this.addVertex(vertexProps);
            }
            triangle.indices[i] = vertexKeyIndex;
            // if the current polyface exists, map the vertex key index to the visitor's client point index
            if (this.currentPolyface !== undefined)
                this.currentPolyface.vertexIndexMap.set(vertexKeyIndex, visitor.clientPointIndex(vertexProps.sourceIndex));
        });
        return triangle;
    }
    /** removed Feature for now */
    addPolyline(points, fillColor, feature) {
        const { mesh } = this;
        const poly = new core_common_1.MeshPolyline();
        for (const position of points)
            poly.addIndex(this.addVertex({ position, fillColor, feature }));
        mesh.addPolyline(poly);
    }
    /** removed Feature for now */
    addPointString(points, fillColor, feature) {
        const { mesh } = this;
        const poly = new core_common_1.MeshPolyline();
        for (const position of points)
            poly.addIndex(this.addVertex({ position, fillColor, feature }));
        mesh.addPolyline(poly);
    }
    beginPolyface(polyface, options) {
        if (!options.generateNoEdges) {
            const triangles = this.mesh.triangles;
            this._currentPolyface = new MeshBuilderPolyface(polyface, options, triangles === undefined ? 0 : triangles.length);
        }
    }
    endPolyface() {
        const { currentPolyface, mesh } = this;
        if (undefined === currentPolyface)
            return;
        this._currentPolyface = undefined;
        buildMeshEdges(mesh, currentPolyface);
    }
    addVertex(vertex, addToMeshOnInsert = true) {
        // if vertex key isn't duplicate, then also insert properties into mesh
        const onInsert = (vk) => this.mesh.addVertex(vk);
        return this.vertexMap.insertKey(vertex, addToMeshOnInsert ? onInsert : undefined);
    }
    addTriangle(triangle) {
        // Attempt to avoid adding vertices originating from degenerate triangles before we get here.
        // Removed assert and just return if degenerate at this point because uncommon cases (not worth testing for) can still occur.
        if (triangle.isDegenerate)
            return;
        const onInsert = (_vk) => this.mesh.addTriangle(triangle);
        this.triangleSet.insertKey(triangle, onInsert);
    }
}
exports.MeshBuilder = MeshBuilder;
/** @internal */
class MeshEdgeCreationOptions {
    get generateAllEdges() { return this.type === MeshEdgeCreationOptions.Type.AllEdges; }
    get generateNoEdges() { return this.type === MeshEdgeCreationOptions.Type.NoEdges; }
    get generateCreaseEdges() { return 0 !== (this.type & MeshEdgeCreationOptions.Type.CreaseEdges); }
    /** Create edge chains for polyfaces that do not already have them. */
    get createEdgeChains() { return 0 !== (this.type & MeshEdgeCreationOptions.Type.CreateChains); }
    constructor(type = MeshEdgeCreationOptions.Type.NoEdges) {
        this.minCreaseAngle = 20.0 * core_geometry_1.Angle.radiansPerDegree;
        this.type = type;
    }
}
exports.MeshEdgeCreationOptions = MeshEdgeCreationOptions;
/** @internal */
(function (MeshEdgeCreationOptions) {
    let Type;
    (function (Type) {
        Type[Type["NoEdges"] = 0] = "NoEdges";
        Type[Type["CreaseEdges"] = 2] = "CreaseEdges";
        Type[Type["SmoothEdges"] = 4] = "SmoothEdges";
        Type[Type["CreateChains"] = 8] = "CreateChains";
        Type[Type["DefaultEdges"] = 2] = "DefaultEdges";
        Type[Type["AllEdges"] = 6] = "AllEdges";
    })(Type = MeshEdgeCreationOptions.Type || (MeshEdgeCreationOptions.Type = {}));
})(MeshEdgeCreationOptions || (exports.MeshEdgeCreationOptions = MeshEdgeCreationOptions = {}));
/** @internal */
class MeshBuilderPolyface {
    constructor(polyface, edgeOptions, baseTriangleIndex) {
        this.vertexIndexMap = new Map();
        this.polyface = polyface;
        this.edgeOptions = edgeOptions;
        this.baseTriangleIndex = baseTriangleIndex;
    }
}
exports.MeshBuilderPolyface = MeshBuilderPolyface;
class EdgeInfo {
    constructor(visible, faceIndex0, edge, point0, point1) {
        this.visible = visible;
        this.faceIndex0 = faceIndex0;
        this.edge = edge;
        this.point0 = point0;
        this.point1 = point1;
    }
    addFace(visible, faceIndex) {
        if (undefined === this.faceIndex1) {
            this.visible ||= visible;
            this.faceIndex1 = faceIndex;
        }
    }
}
function buildMeshEdges(mesh, polyface) {
    if (!mesh.triangles)
        return;
    const edgeMap = new core_bentley_1.Dictionary((lhs, rhs) => lhs.compareTo(rhs));
    const triangleNormals = [];
    // We need to detect the edge pairs -- Can't do that from the Mesh indices as these are not shared - so we'll
    // assume that the polyface indices are properly shared, this should be true as a seperate index array is used
    // for Polyfaces.
    const triangle = new Primitives_1.Triangle();
    const polyfacePoints = [new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d()];
    const polyfaceIndices = [0, 0, 0];
    for (let triangleIndex = polyface.baseTriangleIndex; triangleIndex < mesh.triangles.length; triangleIndex++) {
        let indexNotFound = false;
        mesh.triangles.getTriangle(triangleIndex, triangle);
        for (let j = 0; j < 3; j++) {
            const foundPolyfaceIndex = polyface.vertexIndexMap.get(triangle.indices[j]);
            (0, core_bentley_1.assert)(undefined !== foundPolyfaceIndex);
            if (undefined === foundPolyfaceIndex) {
                indexNotFound = true;
                continue;
            }
            polyfaceIndices[j] = foundPolyfaceIndex;
            polyface.polyface.data.getPoint(foundPolyfaceIndex, polyfacePoints[j]);
        }
        if (indexNotFound)
            continue;
        for (let j = 0; j < 3; j++) {
            const jNext = (j + 1) % 3;
            const triangleNormalIndex = triangleNormals.length;
            const meshEdge = new core_common_1.MeshEdge(triangle.indices[j], triangle.indices[jNext]);
            const polyfaceEdge = new core_common_1.MeshEdge(polyfaceIndices[j], polyfaceIndices[jNext]);
            const edgeInfo = new EdgeInfo(triangle.isEdgeVisible(j), triangleNormalIndex, meshEdge, polyfacePoints[j], polyfacePoints[jNext]);
            const findOrInsert = edgeMap.findOrInsert(polyfaceEdge, edgeInfo);
            if (!findOrInsert.inserted)
                findOrInsert.value.addFace(edgeInfo.visible, triangleNormalIndex);
        }
        const normal = core_geometry_1.Vector3d.createCrossProductToPoints(polyfacePoints[0], polyfacePoints[1], polyfacePoints[2]);
        normal.normalizeInPlace();
        triangleNormals.push(normal);
    }
    // If there is no visibility indication in the mesh, infer from the mesh geometry.
    if (!polyface.edgeOptions.generateAllEdges) {
        const minEdgeDot = Math.cos(polyface.edgeOptions.minCreaseAngle);
        for (const edgeInfo of edgeMap.values()) {
            if (undefined !== edgeInfo.faceIndex1) {
                const normal0 = triangleNormals[edgeInfo.faceIndex0];
                const normal1 = triangleNormals[edgeInfo.faceIndex1];
                if (Math.abs(normal0.dotProduct(normal1)) > minEdgeDot)
                    edgeInfo.visible = false;
            }
        }
    }
    // Now populate the MeshEdges.
    // ###TODO edge chains?
    if (undefined === mesh.edges)
        mesh.edges = new core_common_1.MeshEdges();
    const maxPlanarDot = 0.999999;
    for (const edgeInfo of edgeMap.values()) {
        if (edgeInfo.visible) {
            mesh.edges.visible.push(edgeInfo.edge);
        }
        else if (undefined !== edgeInfo.faceIndex1) {
            const normal0 = triangleNormals[edgeInfo.faceIndex0];
            const normal1 = triangleNormals[edgeInfo.faceIndex1];
            if (Math.abs(normal0.dotProduct(normal1)) < maxPlanarDot) {
                mesh.edges.silhouette.push(edgeInfo.edge);
                mesh.edges.silhouetteNormals.push(new core_common_1.OctEncodedNormalPair(core_common_1.OctEncodedNormal.fromVector(normal0), core_common_1.OctEncodedNormal.fromVector(normal1)));
            }
        }
    }
}
//# sourceMappingURL=MeshBuilder.js.map