"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.VertexTableBuilder = void 0;
exports.createMeshParams = createMeshParams;
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 AuxChannelTable_1 = require("./AuxChannelTable");
const VertexTable_1 = require("./VertexTable");
const SurfaceParams_1 = require("./SurfaceParams");
const VertexIndices_1 = require("./VertexIndices");
const EdgeParams_1 = require("./EdgeParams");
const scratchU32 = new Uint32Array(1);
const scratchF32 = new Float32Array(scratchU32.buffer);
function floatToUint32(val) {
    scratchF32[0] = val;
    return scratchU32[0];
}
function computePolylineCumulativeDistances(args) {
    if (args.cumulativeDistances !== undefined)
        return args.cumulativeDistances;
    const positions = [];
    if (args.points instanceof core_common_1.QPoint3dList) {
        positions.length = args.points.length;
        for (let i = 0; i < args.points.length; i++)
            positions[i] = args.points.list[i].unquantize(args.points.params);
    }
    else {
        positions.length = args.points.length;
        for (let i = 0; i < args.points.length; i++)
            positions[i] = args.points[i];
    }
    const cumulativeDistances = new Float32Array(positions.length);
    cumulativeDistances.fill(Number.NaN);
    for (const line of args.polylines) {
        if (line.length < 2)
            continue;
        let dist = 0.0;
        for (let i = 0; i < line.length; i++) {
            const idx = line[i];
            if (i > 0) {
                const p0 = positions[line[i - 1]];
                const p1 = positions[idx];
                dist += p0.distance(p1);
            }
            // Preserve the first assignment if a vertex is shared across lines.
            if (Number.isNaN(cumulativeDistances[idx]))
                cumulativeDistances[idx] = dist;
        }
    }
    for (let i = 0; i < cumulativeDistances.length; i++) {
        if (Number.isNaN(cumulativeDistances[i]))
            cumulativeDistances[i] = 0.0;
    }
    return cumulativeDistances;
}
/** @internal */
function createMeshParams(args, maxDimension, enableIndexedEdges) {
    const builder = createMeshBuilder(args);
    const vertices = builder.build(args.colors, args.features, maxDimension);
    const surfaceIndices = VertexIndices_1.VertexIndices.fromArray(args.vertIndices);
    const surface = {
        type: builder.type,
        indices: surfaceIndices,
        fillFlags: args.fillFlags ?? core_common_1.FillFlags.ByView,
        hasBakedLighting: true === args.hasBakedLighting,
        textureMapping: undefined !== args.textureMapping ? { texture: args.textureMapping.texture, alwaysDisplayed: false } : undefined,
        material: (0, SurfaceParams_1.createSurfaceMaterial)(args.material),
    };
    const channels = undefined !== args.auxChannels ? AuxChannelTable_1.AuxChannelTable.fromChannels(args.auxChannels, vertices.numVertices, maxDimension) : undefined;
    const edges = (0, EdgeParams_1.createEdgeParams)({ meshArgs: args, maxWidth: maxDimension, createIndexed: enableIndexedEdges });
    return {
        vertices,
        surface,
        edges,
        isPlanar: !!args.isPlanar,
        auxChannels: channels,
    };
}
/** Builds a VertexTable from some data type supplying the vertex data.
 * @internal
 */
class VertexTableBuilder {
    data;
    _curIndex = 0;
    get uvParams() { return undefined; }
    appendColorTable(colorIndex) {
        if (undefined !== colorIndex.nonUniform) {
            for (const color of colorIndex.nonUniform.colors) {
                this.appendColor(color);
            }
        }
    }
    advance(nBytes) {
        this._curIndex += nBytes;
        (0, core_bentley_1.assert)(undefined !== this.data);
        (0, core_bentley_1.assert)(this._curIndex <= this.data.length);
    }
    append8(val) {
        (0, core_bentley_1.assert)(0 <= val);
        (0, core_bentley_1.assert)(val <= 0xff);
        (0, core_bentley_1.assert)(val === Math.floor(val));
        (0, core_bentley_1.expectDefined)(this.data)[this._curIndex] = val;
        this.advance(1);
    }
    append16(val) {
        this.append8(val & 0x00ff);
        this.append8(val >>> 8);
    }
    append32(val) {
        this.append16(val & 0x0000ffff);
        this.append16(val >>> 16);
    }
    appendColor(tbgr) {
        const colors = core_common_1.ColorDef.getColors(tbgr);
        // invert transparency => alpha
        colors.t = 255 - colors.t;
        // premultiply alpha...
        switch (colors.t) {
            case 0:
                colors.r = colors.g = colors.b = 0;
                break;
            case 255:
                break;
            default: {
                const f = colors.t / 255.0;
                colors.r = Math.floor(colors.r * f + 0.5);
                colors.g = Math.floor(colors.g * f + 0.5);
                colors.b = Math.floor(colors.b * f + 0.5);
                break;
            }
        }
        // Store 32-bit value in little-endian order (red first)
        this.append8(colors.r);
        this.append8(colors.g);
        this.append8(colors.b);
        this.append8(colors.t);
    }
    build(colorIndex, featureIndex, maxDimension) {
        const { numVertices, numRgbaPerVertex } = this;
        const numColors = colorIndex.isUniform ? 0 : colorIndex.numColors;
        const dimensions = (0, VertexTable_1.computeDimensions)(numVertices, numRgbaPerVertex, numColors, maxDimension);
        (0, core_bentley_1.assert)(0 === dimensions.width % numRgbaPerVertex || (0 < numColors && 1 === dimensions.height));
        const data = new Uint8Array(dimensions.width * dimensions.height * 4);
        this.data = data;
        for (let i = 0; i < numVertices; i++)
            this.appendVertex(i);
        this.appendColorTable(colorIndex);
        this.data = undefined;
        return {
            data,
            qparams: this.qparams,
            usesUnquantizedPositions: this.usesUnquantizedPositions,
            width: dimensions.width,
            height: dimensions.height,
            hasTranslucency: colorIndex.hasAlpha,
            uniformColor: colorIndex.uniform,
            numVertices,
            numRgbaPerVertex,
            uvParams: this.uvParams,
            featureIndexType: featureIndex.type,
            uniformFeatureID: featureIndex.type === core_common_1.FeatureIndexType.Uniform ? featureIndex.featureID : undefined,
        };
    }
    static buildFromPolylines(args, maxDimension) {
        const polylines = args.polylines;
        if (polylines.length === 0)
            return undefined;
        const builder = createPolylineBuilder(args);
        return builder.build(args.colors, args.features, maxDimension);
    }
}
exports.VertexTableBuilder = VertexTableBuilder;
var Quantized;
(function (Quantized) {
    /**
     * Supplies vertex data from a PolylineArgs or MeshArgs. Each vertex consists of 12 bytes:
     *  pos.x           00
     *  pos.y           02
     *  pos.z           04
     *  colorIndex      06
     *  featureIndex    08 (24 bits)
     *  materialIndex   0B (for meshes that use a material atlas; otherwise unused). NOTE: Currently front-end code does not produce material atlases.
     */
    class SimpleBuilder extends VertexTableBuilder {
        args;
        _qpoints;
        constructor(args) {
            super();
            this._qpoints = args.points;
            this.args = args;
            (0, core_bentley_1.assert)(undefined !== this.args.points);
        }
        get numVertices() { return this.args.points.length; }
        get numRgbaPerVertex() { return 3; }
        get usesUnquantizedPositions() { return false; }
        get qparams() {
            return this._qpoints.params;
        }
        appendVertex(vertIndex) {
            this.appendPosition(vertIndex);
            this.appendColorIndex(vertIndex);
            this.appendFeatureIndex(vertIndex);
        }
        appendPosition(vertIndex) {
            this.append16(this._qpoints.list[vertIndex].x);
            this.append16(this._qpoints.list[vertIndex].y);
            this.append16(this._qpoints.list[vertIndex].z);
        }
        appendColorIndex(vertIndex) {
            if (undefined !== this.args.colors.nonUniform) {
                this.append16(this.args.colors.nonUniform.indices[vertIndex]);
            }
            else {
                this.advance(2);
            }
        }
        appendFeatureIndex(vertIndex) {
            if (undefined !== this.args.features.featureIDs) {
                this.append32(this.args.features.featureIDs[vertIndex]);
            }
            else {
                this.advance(4);
            }
        }
    }
    Quantized.SimpleBuilder = SimpleBuilder;
    class PolylineBuilder extends SimpleBuilder {
        _cumDist;
        constructor(args) {
            super(args);
            this._cumDist = computePolylineCumulativeDistances(args);
        }
        get numRgbaPerVertex() { return 4; }
        appendVertex(vertIndex) {
            super.appendVertex(vertIndex);
            this.appendCumulativeDistance(vertIndex);
        }
        appendCumulativeDistance(vertIndex) {
            const dist = vertIndex < this._cumDist.length ? this._cumDist[vertIndex] : 0.0;
            this.append32(floatToUint32(dist));
        }
    }
    Quantized.PolylineBuilder = PolylineBuilder;
    /** Supplies vertex data from a MeshArgs. */
    class MeshBuilder extends SimpleBuilder {
        type;
        constructor(args, type) {
            super(args);
            this.type = type;
        }
        static create(args) {
            if (args.isVolumeClassifier)
                return new MeshBuilder(args, SurfaceParams_1.SurfaceType.VolumeClassifier);
            const isLit = undefined !== args.normals && 0 < args.normals.length;
            const isTextured = undefined !== args.textureMapping;
            let uvParams;
            if (args.textureMapping) {
                const uvRange = core_geometry_1.Range2d.createNull();
                const fpts = args.textureMapping.uvParams;
                const pt2d = new core_geometry_1.Point2d();
                if (undefined !== fpts && fpts.length > 0)
                    for (let i = 0; i < args.points.length; i++)
                        uvRange.extendPoint(core_geometry_1.Point2d.create(fpts[i].x, fpts[i].y, pt2d));
                uvParams = core_common_1.QParams2d.fromRange(uvRange);
            }
            if (isLit)
                // If isTextured is true, uvParams will always be defined.
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                return isTextured ? new TexturedLitMeshBuilder(args, uvParams) : new LitMeshBuilder(args);
            else
                // If isTextured is true, uvParams will always be defined.
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                return isTextured ? new TexturedMeshBuilder(args, uvParams) : new MeshBuilder(args, SurfaceParams_1.SurfaceType.Unlit);
        }
    }
    Quantized.MeshBuilder = MeshBuilder;
    /** Supplies vertex data from a MeshArgs where each vertex consists of 16 bytes.
     * In addition to the SimpleBuilder data, the final 4 bytes hold the quantized UV params
     * The color index is left uninitialized as it is unused.
     */
    class TexturedMeshBuilder extends MeshBuilder {
        _qparams;
        _qpoint = new core_common_1.QPoint2d();
        constructor(args, qparams, type = SurfaceParams_1.SurfaceType.Textured) {
            super(args, type);
            this._qparams = qparams;
            (0, core_bentley_1.assert)(undefined !== args.textureMapping);
        }
        get numRgbaPerVertex() { return 4; }
        get uvParams() { return this._qparams; }
        appendVertex(vertIndex) {
            this.appendPosition(vertIndex);
            this.appendNormal(vertIndex);
            this.appendFeatureIndex(vertIndex);
            this.appendUVParams(vertIndex);
        }
        appendNormal(_vertIndex) { this.advance(2); } // no normal for unlit meshes
        appendUVParams(vertIndex) {
            this._qpoint.init((0, core_bentley_1.expectDefined)(this.args.textureMapping).uvParams[vertIndex], this._qparams);
            this.append16(this._qpoint.x);
            this.append16(this._qpoint.y);
        }
    }
    /** As with TexturedMeshBuilder, but the color index is replaced with the oct-encoded normal value. */
    class TexturedLitMeshBuilder extends TexturedMeshBuilder {
        constructor(args, qparams) {
            super(args, qparams, SurfaceParams_1.SurfaceType.TexturedLit);
            (0, core_bentley_1.assert)(undefined !== args.normals);
        }
        appendNormal(vertIndex) { this.append16((0, core_bentley_1.expectDefined)(this.args.normals)[vertIndex].value); }
    }
    /** 16 bytes. The last 2 bytes are unused; the 2 immediately preceding it hold the oct-encoded normal value. */
    class LitMeshBuilder extends MeshBuilder {
        constructor(args) {
            super(args, SurfaceParams_1.SurfaceType.Lit);
            (0, core_bentley_1.assert)(undefined !== args.normals);
        }
        get numRgbaPerVertex() { return 4; }
        appendVertex(vertIndex) {
            super.appendVertex(vertIndex);
            this.append16((0, core_bentley_1.expectDefined)(this.args.normals)[vertIndex].value);
            this.advance(2); // 2 unused bytes
        }
    }
})(Quantized || (Quantized = {}));
/** Builders in this namespace store vertex positions as 32-bit floats instead of quantizing to 16-bit unsigned integers.
 * This is preferred for decoration graphics, which might contain ranges of positions that exceed the limits for quantization; if quantized,
 * they could produce visual artifacts.
 * Each builder produces a VertexTable that starts with the following layout:
 *  pos.x:        00
 *  pos.y:        04
 *  pos.z:        08
 *  featureIndex: 0C
 *  materialIndex:0F (NOTE: frontend code currently doesn't produce material atlases, so this is always zero).
 * Followed (by default) by:
 *  colorIndex:   10
 *  unused:       12
 * Subclasses may add 4 more bytes and/or overwrite the final 4 bytes above.
 */
var Unquantized;
(function (Unquantized) {
    const u32Array = new Uint32Array(1);
    const f32Array = new Float32Array(u32Array.buffer);
    // colorIndex:  10
    // unused:      12
    class SimpleBuilder extends VertexTableBuilder {
        args;
        _points;
        _qparams3d;
        constructor(args) {
            super();
            (0, core_bentley_1.assert)(!(args.points instanceof core_common_1.QPoint3dList));
            this._qparams3d = core_common_1.QParams3d.fromRange(args.points.range);
            this.args = args;
            this._points = args.points;
        }
        get numVertices() { return this._points.length; }
        get numRgbaPerVertex() { return 5; }
        get usesUnquantizedPositions() { return true; }
        get qparams() { return this._qparams3d; }
        appendVertex(vertIndex) {
            this.appendTransposePosAndFeatureNdx(vertIndex);
            this.appendColorIndex(vertIndex);
        }
        appendFloat32(val) {
            f32Array[0] = val;
            this.append32(u32Array[0]);
        }
        convertFloat32(val) {
            f32Array[0] = val;
            return u32Array[0];
        }
        appendTransposePosAndFeatureNdx(vertIndex) {
            // transpose position xyz vals into [0].xyz - [3].xyz, and add feature index at .w
            // this is to order things to let shader code access much more efficiently
            const pt = this._points[vertIndex];
            const x = this.convertFloat32(pt.x);
            const y = this.convertFloat32(pt.y);
            const z = this.convertFloat32(pt.z);
            const featID = (this.args.features.featureIDs) ? this.args.features.featureIDs[vertIndex] : 0;
            this.append8(x & 0x000000ff);
            this.append8(y & 0x000000ff);
            this.append8(z & 0x000000ff);
            this.append8(featID & 0x000000ff);
            this.append8((x >>> 8) & 0x000000ff);
            this.append8((y >>> 8) & 0x000000ff);
            this.append8((z >>> 8) & 0x000000ff);
            this.append8((featID >>> 8) & 0x000000ff);
            this.append8((x >>> 16) & 0x000000ff);
            this.append8((y >>> 16) & 0x000000ff);
            this.append8((z >>> 16) & 0x000000ff);
            this.append8((featID >>> 16) & 0x000000ff);
            this.append8(x >>> 24);
            this.append8(y >>> 24);
            this.append8(z >>> 24);
            this.append8(featID >>> 24);
        }
        appendPosition(vertIndex) {
            const pt = this._points[vertIndex];
            this.appendFloat32(pt.x);
            this.appendFloat32(pt.y);
            this.appendFloat32(pt.z);
        }
        appendFeatureIndex(vertIndex) {
            if (this.args.features.featureIDs)
                this.append32(this.args.features.featureIDs[vertIndex]);
            else
                this.advance(4);
        }
        _appendColorIndex(vertIndex) {
            if (undefined !== this.args.colors.nonUniform)
                this.append16(this.args.colors.nonUniform.indices[vertIndex]);
            else
                this.advance(2);
        }
        appendColorIndex(vertIndex) {
            this._appendColorIndex(vertIndex);
            this.advance(2);
        }
    }
    Unquantized.SimpleBuilder = SimpleBuilder;
    class PolylineBuilder extends SimpleBuilder {
        _cumDist;
        constructor(args) {
            super(args);
            this._cumDist = computePolylineCumulativeDistances(args);
        }
        get numRgbaPerVertex() { return 6; }
        appendVertex(vertIndex) {
            super.appendVertex(vertIndex);
            this.appendCumulativeDistance(vertIndex);
        }
        appendCumulativeDistance(vertIndex) {
            const dist = vertIndex < this._cumDist.length ? this._cumDist[vertIndex] : 0.0;
            this.append32(floatToUint32(dist));
        }
    }
    Unquantized.PolylineBuilder = PolylineBuilder;
    class MeshBuilder extends SimpleBuilder {
        type;
        constructor(args, type) {
            super(args);
            this.type = type;
        }
        static create(args) {
            if (args.isVolumeClassifier)
                return new MeshBuilder(args, SurfaceParams_1.SurfaceType.VolumeClassifier);
            const isLit = undefined !== args.normals && 0 < args.normals.length;
            const isTextured = undefined !== args.textureMapping;
            let uvParams;
            if (args.textureMapping) {
                const uvRange = core_geometry_1.Range2d.createNull();
                const fpts = args.textureMapping.uvParams;
                const pt2d = new core_geometry_1.Point2d();
                if (undefined !== fpts && fpts.length > 0)
                    for (let i = 0; i < args.points.length; i++)
                        uvRange.extendPoint(core_geometry_1.Point2d.create(fpts[i].x, fpts[i].y, pt2d));
                uvParams = core_common_1.QParams2d.fromRange(uvRange);
            }
            if (isLit)
                // If isTextured is true, uvParams will always be defined.
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                return isTextured ? new TexturedLitMeshBuilder(args, uvParams) : new LitMeshBuilder(args);
            else
                // If isTextured is true, uvParams will always be defined.
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                return isTextured ? new TexturedMeshBuilder(args, uvParams) : new MeshBuilder(args, SurfaceParams_1.SurfaceType.Unlit);
        }
    }
    Unquantized.MeshBuilder = MeshBuilder;
    // u: 10
    // v: 12
    class TexturedMeshBuilder extends MeshBuilder {
        _qparams;
        _qpoint = new core_common_1.QPoint2d();
        constructor(args, qparams, type = SurfaceParams_1.SurfaceType.Textured) {
            super(args, type);
            this._qparams = qparams;
            (0, core_bentley_1.assert)(undefined !== args.textureMapping);
        }
        get uvParams() { return this._qparams; }
        appendVertex(vertIndex) {
            super.appendVertex(vertIndex);
            this._qpoint.init((0, core_bentley_1.expectDefined)(this.args.textureMapping).uvParams[vertIndex], this._qparams);
            this.append16(this._qpoint.x);
            this.append16(this._qpoint.y);
        }
        appendColorIndex() { }
    }
    // u: 10
    // v: 12
    // normal: 14
    // unused: 16
    class TexturedLitMeshBuilder extends TexturedMeshBuilder {
        constructor(args, qparams) {
            super(args, qparams, SurfaceParams_1.SurfaceType.TexturedLit);
            (0, core_bentley_1.assert)(undefined !== args.normals);
        }
        get numRgbaPerVertex() { return 6; }
        appendVertex(vertIndex) {
            super.appendVertex(vertIndex);
            this.append16((0, core_bentley_1.expectDefined)(this.args.normals)[vertIndex].value);
            this.advance(2);
        }
    }
    // color: 10
    // normal: 12
    class LitMeshBuilder extends MeshBuilder {
        constructor(args) {
            super(args, SurfaceParams_1.SurfaceType.Lit);
            (0, core_bentley_1.assert)(undefined !== args.normals);
        }
        appendColorIndex(vertIndex) {
            super._appendColorIndex(vertIndex);
        }
        appendVertex(vertIndex) {
            super.appendVertex(vertIndex);
            this.append16((0, core_bentley_1.expectDefined)(this.args.normals)[vertIndex].value);
        }
    }
})(Unquantized || (Unquantized = {}));
function createMeshBuilder(args) {
    if (args.points instanceof core_common_1.QPoint3dList)
        return Quantized.MeshBuilder.create(args);
    else
        return Unquantized.MeshBuilder.create(args);
}
function createPolylineBuilder(args) {
    const quantized = args.points instanceof core_common_1.QPoint3dList;
    if (args.flags.isDisjoint) {
        return quantized
            ? new Quantized.SimpleBuilder(args)
            : new Unquantized.SimpleBuilder(args);
    }
    return quantized
        ? new Quantized.PolylineBuilder(args)
        : new Unquantized.PolylineBuilder(args);
}
//# sourceMappingURL=VertexTableBuilder.js.map