/*---------------------------------------------------------------------------------------------
* 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 { assert } from "@itwin/core-bentley";
import { AuxChannel, AuxChannelData } from "@itwin/core-geometry";
import { ColorIndex, EdgeArgs, FeatureIndex, FeatureIndexType, LinePixels, PolylineEdgeArgs, PolylineTypeFlags, QParams3d, QPoint3dList, SilhouetteEdgeArgs, } from "@itwin/core-common";
import { ColorMap } from "./ColorMap";
import { DisplayParams } from "./DisplayParams";
import { MeshPrimitiveType } from "./MeshPrimitive";
import { TriangleList } from "./Primitives";
export function createPolylineArgs(mesh) {
    if (!mesh.polylines || mesh.polylines.length === 0)
        return undefined;
    const polylines = [];
    for (const polyline of mesh.polylines)
        if (polyline.indices.length > 0)
            polylines.push(polyline.indices);
    if (polylines.length === 0)
        return undefined;
    const flags = {
        is2d: mesh.is2d,
        isPlanar: mesh.isPlanar,
        isDisjoint: mesh.type === MeshPrimitiveType.Point,
    };
    if (mesh.displayParams.regionEdgeType === DisplayParams.RegionEdgeType.Outline) {
        // This polyline is behaving as the edges of a region surface.
        if (!mesh.displayParams.gradient || mesh.displayParams.gradient.isOutlined)
            flags.type = PolylineTypeFlags.Edge;
        else
            flags.type = PolylineTypeFlags.Outline; // edges only displayed if fill undisplayed
    }
    const colors = new ColorIndex();
    mesh.colorMap.toColorIndex(colors, mesh.colors);
    const features = new FeatureIndex();
    mesh.toFeatureIndex(features);
    return {
        width: mesh.displayParams.width,
        linePixels: mesh.displayParams.linePixels,
        flags,
        polylines,
        points: mesh.points,
        colors,
        features,
        cumulativeDistances: mesh.cumulativeDistances,
    };
}
/** The vertices of the edges are shared with those of the surface. */
export class MeshArgsEdges {
    edges = new EdgeArgs();
    silhouettes = new SilhouetteEdgeArgs();
    polylines = new PolylineEdgeArgs();
    width = 0;
    linePixels = LinePixels.Solid;
    color;
    clear() {
        this.edges.clear();
        this.silhouettes.clear();
        this.polylines.clear();
        this.width = 0;
        this.linePixels = LinePixels.Solid;
    }
    get isValid() { return this.edges.isValid || this.silhouettes.isValid || this.polylines.isValid; }
}
export function createMeshArgs(mesh) {
    if (!mesh.triangles || mesh.triangles.isEmpty || mesh.points.length === 0)
        return undefined;
    const texture = mesh.displayParams.textureMapping?.texture;
    const useConstantLod = mesh.displayParams.textureMapping?.params?.useConstantLod;
    const constantLodParams = mesh.displayParams.textureMapping?.params?.constantLodParams;
    const textureMapping = texture && mesh.uvParams.length > 0 ? { texture, uvParams: mesh.uvParams, useConstantLod, constantLodParams } : undefined;
    const colors = new ColorIndex();
    mesh.colorMap.toColorIndex(colors, mesh.colors);
    const features = new FeatureIndex();
    mesh.toFeatureIndex(features);
    let edges;
    if (mesh.edges) {
        edges = new MeshArgsEdges();
        edges.width = mesh.edges.appearance?.width ?? mesh.displayParams.width;
        edges.linePixels = mesh.edges.appearance?.linePixels ?? mesh.displayParams.linePixels;
        edges.edges.init(mesh.edges);
        edges.silhouettes.init(mesh.edges);
        edges.color = mesh.edges.appearance?.color;
        edges.polylines.init(mesh.edges.polylineGroups);
    }
    return {
        vertIndices: mesh.triangles.indices,
        points: mesh.points,
        normals: !mesh.displayParams.ignoreLighting && mesh.normals.length > 0 ? mesh.normals : undefined,
        textureMapping,
        colors,
        features,
        material: mesh.displayParams.material,
        fillFlags: mesh.displayParams.fillFlags,
        isPlanar: mesh.isPlanar,
        is2d: mesh.is2d,
        hasBakedLighting: true === mesh.hasBakedLighting,
        isVolumeClassifier: true === mesh.isVolumeClassifier,
        edges,
        auxChannels: mesh.auxChannels,
    };
}
export class Mesh {
    _data;
    points;
    normals = [];
    uvParams = [];
    colorMap = new ColorMap(); // used to be called ColorTable
    colors = [];
    cumulativeDistances;
    edges;
    features;
    type;
    is2d;
    isPlanar;
    hasBakedLighting;
    isVolumeClassifier;
    displayParams;
    _auxChannels;
    constructor(props) {
        const { displayParams, features, type, range, is2d, isPlanar } = props;
        this._data = MeshPrimitiveType.Mesh === type ? new TriangleList() : [];
        this.displayParams = displayParams;
        this.features = features ? new Mesh.Features(features) : undefined;
        this.type = type;
        this.is2d = is2d;
        this.isPlanar = isPlanar;
        this.hasBakedLighting = (true === props.hasBakedLighting);
        this.isVolumeClassifier = (true === props.isVolumeClassifier);
        if (props.quantizePositions) {
            this.points = new QPoint3dList(QParams3d.fromRange(range));
        }
        else {
            const points = [];
            points.range = range;
            const center = range.center;
            points.add = (pt) => {
                // assert(range.containsPoint(pt)); rounding error triggers this sometimes...
                points.push(pt.minus(center));
            };
            this.points = points;
        }
    }
    static create(props) { return new Mesh(props); }
    get triangles() {
        return MeshPrimitiveType.Mesh === this.type ? this._data : undefined;
    }
    get polylines() {
        return MeshPrimitiveType.Mesh !== this.type ? this._data : undefined;
    }
    get auxChannels() {
        return this._auxChannels;
    }
    addAuxChannels(channels, srcIndex) {
        // The native version of this function appears to assume that all polyfaces added to the Mesh will have
        // the same number + type of aux channels.
        // ###TODO We should really produce a separate Mesh for each unique combination. For now just bail on mismatch.
        if (this._auxChannels) {
            if (this._auxChannels.length !== channels.length)
                return;
            for (let i = 0; i < channels.length; i++) {
                const src = channels[i];
                const dst = this._auxChannels[i];
                if (src.dataType !== dst.dataType || src.name !== dst.name || src.inputName !== dst.inputName)
                    return;
            }
        }
        if (!this._auxChannels) {
            // Copy the channels, leaving each AuxData's values array empty.
            this._auxChannels = channels.map((x) => new AuxChannel(x.data.map((y) => new AuxChannelData(y.input, [])), x.dataType, x.name, x.inputName));
        }
        // Append the value at srcIndex from each source channel's data to our channels.
        for (let channelIndex = 0; channelIndex < channels.length; channelIndex++) {
            const srcChannel = channels[channelIndex];
            const dstChannel = this._auxChannels[channelIndex];
            const dstIndex = dstChannel.valueCount;
            for (let dataIndex = 0; dataIndex < srcChannel.data.length; dataIndex++) {
                const dstData = dstChannel.data[dataIndex];
                dstData.copyValues(srcChannel.data[dataIndex], dstIndex, srcIndex, dstChannel.entriesPerValue);
            }
        }
    }
    toFeatureIndex(index) {
        if (undefined !== this.features)
            this.features.toFeatureIndex(index);
    }
    toMeshArgs() {
        return createMeshArgs(this);
    }
    toPolylineArgs() {
        return createPolylineArgs(this);
    }
    addPolyline(poly) {
        const { type, polylines } = this;
        assert(MeshPrimitiveType.Polyline === type || MeshPrimitiveType.Point === type);
        assert(undefined !== polylines);
        if (MeshPrimitiveType.Polyline === type && poly.indices.length < 2)
            return;
        if (undefined !== polylines)
            polylines.push(poly);
    }
    addTriangle(triangle) {
        const { triangles, type } = this;
        assert(MeshPrimitiveType.Mesh === type);
        assert(undefined !== triangles);
        if (undefined !== triangles)
            triangles.addTriangle(triangle);
    }
    addVertex(props) {
        const { feature, position, normal, uvParam, fillColor } = props;
        this.points.add(position);
        if (undefined !== normal)
            this.normals.push(normal);
        if (undefined !== uvParam)
            this.uvParams.push(uvParam);
        if (feature) {
            assert(undefined !== this.features);
            this.features.add(feature, this.points.length);
        }
        // Don't allocate color indices until we have non-uniform colors
        if (0 === this.colorMap.length) {
            this.colorMap.insert(fillColor);
            assert(this.colorMap.isUniform);
            assert(0 === this.colorMap.indexOf(fillColor));
        }
        else if (!this.colorMap.isUniform || !this.colorMap.hasColor(fillColor)) {
            // Back-fill uniform value (index=0) for existing vertices if previously uniform
            if (0 === this.colors.length)
                this.colors.length = this.points.length - 1;
            this.colors.push(this.colorMap.insert(fillColor));
            assert(!this.colorMap.isUniform);
        }
        return this.points.length - 1;
    }
}
(function (Mesh) {
    class Features {
        table;
        indices = [];
        uniform = 0;
        initialized = false;
        constructor(table) { this.table = table; }
        add(feat, numVerts) {
            const index = this.table.insert(feat);
            if (!this.initialized) {
                // First feature - uniform.
                this.uniform = index;
                this.initialized = true;
            }
            else if (0 < this.indices.length) {
                // Already non-uniform
                this.indices.push(index);
            }
            else {
                // Second feature - back-fill uniform for existing verts
                while (this.indices.length < numVerts - 1)
                    this.indices.push(this.uniform);
                this.indices.push(index);
            }
        }
        setIndices(indices) {
            this.indices.length = 0;
            this.uniform = 0;
            this.initialized = 0 < indices.length;
            assert(0 < indices.length);
            if (1 === indices.length)
                this.uniform = indices[0];
            else if (1 < indices.length)
                this.indices = indices;
        }
        toFeatureIndex(output) {
            const index = output ?? new FeatureIndex();
            if (!this.initialized) {
                index.type = FeatureIndexType.Empty;
            }
            else if (this.indices.length === 0) {
                index.type = FeatureIndexType.Uniform;
                index.featureID = this.uniform;
            }
            else {
                index.type = FeatureIndexType.NonUniform;
                index.featureIDs = new Uint32Array(this.indices);
            }
            return index;
        }
    }
    Mesh.Features = Features;
})(Mesh || (Mesh = {}));
export class MeshList extends Array {
    features;
    range;
    constructor(features, range) {
        super();
        this.features = features;
        this.range = range;
    }
}
//# sourceMappingURL=MeshPrimitives.js.map