/*---------------------------------------------------------------------------------------------
* 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 { PolylineTypeFlags, QPoint3dList } from "@itwin/core-common";
import { VertexIndices } from "./VertexIndices";
import { Vector3d } from "@itwin/core-geometry";
import { assert } from "@itwin/core-bentley";
import { VertexTableBuilder } from "./VertexTableBuilder";
/** @internal */
export function tesselatePolylineFromMesh(args) {
    const tesselator = PolylineTesselator.fromMesh(args);
    return tesselator?.tesselate();
}
class PolylineVertex {
    constructor() {
        this.isSegmentStart = false;
        this.isPolylineStartOrEnd = false;
        this.vertexIndex = 0;
        this.prevIndex = 0;
        this.nextIndex = 0;
    }
    init(isSegmentStart, isPolylineStartOrEnd, vertexIndex, prevIndex, nextIndex) {
        this.isSegmentStart = isSegmentStart;
        this.isPolylineStartOrEnd = isPolylineStartOrEnd;
        this.vertexIndex = vertexIndex;
        this.prevIndex = prevIndex;
        this.nextIndex = nextIndex;
    }
    computeParam(negatePerp, adjacentToJoint = false, joint = false, noDisplacement = false) {
        if (joint)
            return 12 /* PolylineParam.kJointBase */;
        let param;
        if (noDisplacement)
            param = 96 /* PolylineParam.kNoneAdjustWeight */; // prevent getting tossed before width adjustment
        else if (adjacentToJoint)
            param = 9 /* PolylineParam.kMiterInsideOnly */;
        else
            param = this.isPolylineStartOrEnd ? 3 /* PolylineParam.kSquare */ : 6 /* PolylineParam.kMiter */;
        let adjust = 0;
        if (negatePerp)
            adjust = 24 /* PolylineParam.kNegatePerp */;
        if (!this.isSegmentStart)
            adjust += 48 /* PolylineParam.kNegateAlong */;
        return param + adjust;
    }
}
class PolylineTesselator {
    constructor(polylines, points, doJointTriangles) {
        this._numIndices = 0;
        this._vertIndex = [];
        this._prevIndex = [];
        this._nextIndex = [];
        this._nextParam = [];
        this._position = [];
        this._polylines = polylines;
        if (points instanceof QPoint3dList) {
            for (const p of points.list)
                this._position.push(p.unquantize(points.params));
        }
        else {
            this._position = points;
        }
        this._doJoints = doJointTriangles;
    }
    static fromPolyline(args) {
        return new PolylineTesselator(args.polylines, args.points, wantJointTriangles(args.width, !!args.flags.is2d));
    }
    static fromMesh(args) {
        if (undefined !== args.edges?.polylines.lines && undefined !== args.points)
            return new PolylineTesselator(args.edges.polylines.lines, args.points, wantJointTriangles(args.edges.width, true === args.is2d));
        return undefined;
    }
    tesselate() {
        this._tesselate();
        const vertIndex = VertexIndices.fromArray(this._vertIndex);
        const prevIndex = VertexIndices.fromArray(this._prevIndex);
        const nextIndexAndParam = new Uint8Array(this._numIndices * 4);
        for (let i = 0; i < this._numIndices; i++) {
            const index = this._nextIndex[i];
            const j = i * 4;
            VertexIndices.encodeIndex(index, nextIndexAndParam, j);
            nextIndexAndParam[j + 3] = this._nextParam[i] & 0x000000ff;
        }
        return {
            indices: vertIndex,
            prevIndices: prevIndex,
            nextIndicesAndParams: nextIndexAndParam,
        };
    }
    _tesselate() {
        const v0 = new PolylineVertex(), v1 = new PolylineVertex();
        const maxJointDot = -0.7;
        for (const line of this._polylines) {
            if (line.length < 2)
                continue;
            const last = line.length - 1;
            const isClosed = line[0] === line[last];
            for (let i = 0; i < last; ++i) {
                const idx0 = line[i];
                const idx1 = line[i + 1];
                const isStart = (0 === i);
                const isEnd = (last - 1 === i);
                const prevIdx0 = isStart ? (isClosed ? line[last - 1] : idx0) : line[i - 1];
                const nextIdx1 = isEnd ? (isClosed ? line[1] : idx1) : line[i + 2];
                v0.init(true, isStart && !isClosed, idx0, prevIdx0, idx1);
                v1.init(false, isEnd && !isClosed, idx1, nextIdx1, idx0);
                const jointAt0 = this._doJoints && (isClosed || !isStart) && this._dotProduct(v0) > maxJointDot;
                const jointAt1 = this._doJoints && (isClosed || !isEnd) && this._dotProduct(v1) > maxJointDot;
                if (jointAt0 || jointAt1) {
                    this._addVertex(v0, v0.computeParam(true, jointAt0, false, false));
                    this._addVertex(v1, v1.computeParam(false, jointAt1, false, false));
                    this._addVertex(v0, v0.computeParam(false, jointAt0, false, true));
                    this._addVertex(v0, v0.computeParam(false, jointAt0, false, true));
                    this._addVertex(v1, v1.computeParam(false, jointAt1, false, false));
                    this._addVertex(v1, v1.computeParam(false, jointAt1, false, true));
                    this._addVertex(v0, v0.computeParam(false, jointAt0, false, true));
                    this._addVertex(v1, v1.computeParam(false, jointAt1, false, true));
                    this._addVertex(v0, v0.computeParam(false, jointAt0, false, false));
                    this._addVertex(v0, v0.computeParam(false, jointAt0, false, false));
                    this._addVertex(v1, v1.computeParam(false, jointAt1, false, true));
                    this._addVertex(v1, v1.computeParam(true, jointAt1, false, false));
                    if (jointAt0)
                        this.addJointTriangles(v0, v0.computeParam(false, true, false, true), v0);
                    if (jointAt1)
                        this.addJointTriangles(v1, v1.computeParam(false, true, false, true), v1);
                }
                else {
                    this._addVertex(v0, v0.computeParam(true));
                    this._addVertex(v1, v1.computeParam(false));
                    this._addVertex(v0, v0.computeParam(false));
                    this._addVertex(v0, v0.computeParam(false));
                    this._addVertex(v1, v1.computeParam(false));
                    this._addVertex(v1, v1.computeParam(true));
                }
            }
        }
    }
    addJointTriangles(v0, p0, v1) {
        const param = v1.computeParam(false, false, true);
        for (let i = 0; i < 3; i++) {
            this._addVertex(v0, p0);
            this._addVertex(v1, param + i + 1);
            this._addVertex(v1, param + i);
        }
    }
    _dotProduct(v) {
        const pos = this._position[v.vertexIndex];
        const prevDir = Vector3d.createStartEnd(this._position[v.prevIndex], pos);
        const nextDir = Vector3d.createStartEnd(this._position[v.nextIndex], pos);
        return prevDir.dotProduct(nextDir);
    }
    _addVertex(vertex, param) {
        this._vertIndex[this._numIndices] = vertex.vertexIndex;
        this._prevIndex[this._numIndices] = vertex.prevIndex;
        this._nextIndex[this._numIndices] = vertex.nextIndex;
        this._nextParam[this._numIndices] = param;
        this._numIndices++;
    }
}
/** Strictly for tests. @internal */
export function tesselatePolyline(polylines, points, doJointTriangles) {
    const tesselator = new PolylineTesselator(polylines, points, doJointTriangles);
    return tesselator.tesselate();
}
/** @internal */
export function createPolylineParams(args, maxDimension) {
    assert(!args.flags.isDisjoint);
    const vertices = VertexTableBuilder.buildFromPolylines(args, maxDimension);
    if (undefined === vertices)
        return undefined;
    const tesselator = PolylineTesselator.fromPolyline(args);
    if (undefined === tesselator)
        return undefined;
    return {
        vertices,
        polyline: tesselator.tesselate(),
        isPlanar: !!args.flags.isPlanar,
        type: args.flags.type ?? PolylineTypeFlags.Normal,
        weight: args.width,
        linePixels: args.linePixels,
    };
}
/** @internal */
export function wantJointTriangles(weight, is2d) {
    // Joints are incredibly expensive. In 3d, only generate them if the line is sufficiently wide for them to be noticeable.
    const jointWidthThreshold = 3;
    return is2d || weight >= jointWidthThreshold;
}
//# sourceMappingURL=PolylineParams.js.map