"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 Tiles
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.toVertexTable = toVertexTable;
exports.edgeParamsFromImdl = edgeParamsFromImdl;
exports.edgeParamsToImdl = edgeParamsToImdl;
exports.toMaterialParams = toMaterialParams;
exports.convertFeatureTable = convertFeatureTable;
exports.parseImdlDocument = parseImdlDocument;
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 MeshPrimitive_1 = require("../internal/render/MeshPrimitive");
const SurfaceParams_1 = require("../internal/render/SurfaceParams");
const DisplayParams_1 = require("../internal/render/DisplayParams");
const AuxChannelTable_1 = require("../internal/render/AuxChannelTable");
const VertexTableSplitter_1 = require("../internal/render/VertexTableSplitter");
const AnimationNodeId_1 = require("../internal/render/AnimationNodeId");
const VertexIndices_1 = require("../internal/render/VertexIndices");
const CompactEdges_1 = require("./CompactEdges");
/** Header preceding "glTF" data in iMdl tile. */
class GltfHeader extends core_common_1.TileHeader {
    get isValid() { return core_common_1.TileFormat.Gltf === this.format; }
    constructor(stream) {
        super(stream);
        this.scenePosition = 0;
        this.sceneStrLength = 0;
        this.binaryPosition = 0;
        this.gltfLength = stream.readUint32();
        this.sceneStrLength = stream.readUint32();
        const value5 = stream.readUint32();
        // Early versions of the reality data tile publisher incorrectly put version 2 into header - handle these old tiles
        // validating the chunk type.
        if (this.version === core_common_1.GltfVersions.Version2 && value5 === core_common_1.GltfVersions.Gltf1SceneFormat)
            this.version = core_common_1.GltfVersions.Version1;
        if (this.version === core_common_1.GltfVersions.Version1) {
            const gltfSceneFormat = value5;
            if (core_common_1.GltfVersions.Gltf1SceneFormat !== gltfSceneFormat) {
                this.invalidate();
                return;
            }
            this.scenePosition = stream.curPos;
            this.binaryPosition = stream.curPos + this.sceneStrLength;
        }
        else if (this.version === core_common_1.GltfVersions.Version2) {
            const sceneChunkType = value5;
            this.scenePosition = stream.curPos;
            stream.curPos = stream.curPos + this.sceneStrLength;
            const binaryLength = stream.readUint32();
            const binaryChunkType = stream.readUint32();
            if (core_common_1.GltfV2ChunkTypes.JSON !== sceneChunkType || core_common_1.GltfV2ChunkTypes.Binary !== binaryChunkType || 0 === binaryLength) {
                this.invalidate();
                return;
            }
            this.binaryPosition = stream.curPos;
        }
        else {
            this.invalidate();
        }
    }
}
const nodeIdRegex = /Node_(.*)/;
function extractNodeId(nodeName) {
    const match = nodeName.match(nodeIdRegex);
    (0, core_bentley_1.assert)(!!match && match.length === 2);
    if (!match || match.length !== 2)
        return 0;
    const nodeId = Number.parseInt(match[1], 10);
    (0, core_bentley_1.assert)(!Number.isNaN(nodeId));
    return Number.isNaN(nodeId) ? 0 : nodeId;
}
class Texture extends core_common_1.RenderTexture {
    constructor(type) {
        super(type);
    }
    dispose() { }
    get bytesUsed() { return 0; }
}
class NamedTexture extends Texture {
    constructor(_name, type) {
        super(type);
        this._name = _name;
    }
    toImdl() {
        return this._name;
    }
}
class GradientTexture extends Texture {
    constructor(_gradient) {
        super(core_common_1.RenderTexture.Type.Normal);
        this._gradient = _gradient;
    }
    toImdl() {
        return this._gradient;
    }
}
class Material extends core_common_1.RenderMaterial {
    toImdl() {
        const material = this.key ?? this.materialParams;
        return { isAtlas: false, material };
    }
    // eslint-disable-next-line @typescript-eslint/no-deprecated
    constructor(params, imdl) {
        super(params);
        this.materialParams = imdl ?? {
            alpha: params.alpha,
            diffuse: {
                color: params.diffuseColor?.toJSON(),
                weight: params.diffuse,
            },
            specular: {
                color: params.specularColor?.toJSON(),
                weight: params.specular,
                exponent: params.specularExponent,
            },
        };
    }
    static create(args) {
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        const params = new core_common_1.RenderMaterial.Params();
        params.alpha = args.alpha;
        if (args.diffuse) {
            if (undefined !== args.diffuse.weight)
                params.diffuse = args.diffuse?.weight;
            if (args.diffuse?.color)
                params.diffuseColor = args.diffuse.color instanceof core_common_1.ColorDef ? args.diffuse.color : core_common_1.RgbColor.fromJSON(args.diffuse.color).toColorDef();
        }
        if (args.specular) {
            if (undefined !== args.specular.weight)
                params.specular = args.specular.weight;
            if (undefined !== args.specular.exponent)
                params.specularExponent = args.specular.exponent;
            if (args.specular.color)
                params.specularColor = args.specular.color instanceof core_common_1.ColorDef ? args.specular.color : core_common_1.RgbColor.fromJSON(args.specular.color).toColorDef();
        }
        return new Material(params);
    }
}
/** @internal */
function toVertexTable(imdl) {
    return {
        ...imdl,
        uniformColor: undefined !== imdl.uniformColor ? core_common_1.ColorDef.fromJSON(imdl.uniformColor) : undefined,
        qparams: core_common_1.QParams3d.fromJSON(imdl.qparams),
        uvParams: imdl.uvParams ? core_common_1.QParams2d.fromJSON(imdl.uvParams) : undefined,
    };
}
function fromVertexTable(table) {
    return {
        ...table,
        uniformColor: table.uniformColor?.toJSON(),
        qparams: table.qparams.toJSON(),
        uvParams: table.uvParams?.toJSON(),
    };
}
/** @internal */
function edgeParamsFromImdl(imdl) {
    return {
        ...imdl,
        segments: imdl.segments ? {
            ...imdl.segments,
            indices: new VertexIndices_1.VertexIndices(imdl.segments.indices),
        } : undefined,
        silhouettes: imdl.silhouettes ? {
            ...imdl.silhouettes,
            indices: new VertexIndices_1.VertexIndices(imdl.silhouettes.indices),
        } : undefined,
        polylines: imdl.polylines ? {
            ...imdl.polylines,
            indices: new VertexIndices_1.VertexIndices(imdl.polylines.indices),
            prevIndices: new VertexIndices_1.VertexIndices(imdl.polylines.prevIndices),
        } : undefined,
        indexed: imdl.indexed ? {
            indices: new VertexIndices_1.VertexIndices(imdl.indexed.indices),
            edges: imdl.indexed.edges,
        } : undefined,
    };
}
/** @internal */
function edgeParamsToImdl(params) {
    return {
        ...params,
        segments: params.segments ? {
            ...params.segments,
            indices: params.segments.indices.data,
        } : undefined,
        silhouettes: params.silhouettes ? {
            ...params.silhouettes,
            indices: params.silhouettes.indices.data,
        } : undefined,
        polylines: params.polylines ? {
            ...params.polylines,
            indices: params.polylines.indices.data,
            prevIndices: params.polylines.prevIndices.data,
        } : undefined,
        indexed: params.indexed ? {
            indices: params.indexed.indices.data,
            edges: params.indexed.edges,
        } : undefined,
    };
}
class Parser {
    constructor(doc, binaryData, options, featureTableInfo, stream) {
        this._patterns = new Map();
        this._document = doc;
        this._binaryData = binaryData;
        this._options = options;
        this._featureTableInfo = featureTableInfo;
        this._stream = stream;
        this._timeline = options.timeline;
    }
    parse() {
        const featureTable = this.parseFeatureTable();
        if (!featureTable)
            return core_common_1.TileReadStatus.InvalidFeatureTable;
        const rtcCenter = this._document.rtcCenter ? {
            x: this._document.rtcCenter[0] ?? 0,
            y: this._document.rtcCenter[1] ?? 0,
            z: this._document.rtcCenter[2] ?? 0,
        } : undefined;
        const primitiveNodes = this.parseNodes(featureTable);
        const nodes = this.groupPrimitiveNodes(primitiveNodes, featureTable);
        return {
            featureTable,
            nodes,
            rtcCenter,
            binaryData: this._binaryData,
            json: this._document,
            patterns: this._patterns,
        };
    }
    parseFeatureTable() {
        this._stream.curPos = this._featureTableInfo.startPos;
        const header = core_common_1.FeatureTableHeader.readFrom(this._stream);
        if (!header || 0 !== header.length % 4)
            return undefined;
        // NB: We make a copy of the sub-array because we don't want to pin the entire data array in memory.
        const numUint32s = (header.length - core_common_1.FeatureTableHeader.sizeInBytes) / 4;
        const packedFeatureArray = new Uint32Array(this._stream.nextUint32s(numUint32s));
        if (this._stream.isPastTheEnd)
            return undefined;
        let featureTable;
        if (this._featureTableInfo.multiModel) {
            featureTable = {
                multiModel: true,
                data: packedFeatureArray,
                numFeatures: header.count,
                numSubCategories: header.numSubCategories,
            };
        }
        else {
            let animNodesArray;
            const animationNodes = this._document.animationNodes;
            if (undefined !== animationNodes) {
                const bytesPerId = core_bentley_1.JsonUtils.asInt(animationNodes.bytesPerId);
                const bufferViewId = core_bentley_1.JsonUtils.asString(animationNodes.bufferView);
                const bufferViewJson = this._document.bufferViews[bufferViewId];
                if (undefined !== bufferViewJson) {
                    const byteOffset = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteOffset);
                    const byteLength = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteLength);
                    const bytes = this._binaryData.subarray(byteOffset, byteOffset + byteLength);
                    switch (bytesPerId) {
                        case 1:
                            animNodesArray = new Uint8Array(bytes);
                            break;
                        case 2:
                            // NB: A *copy* of the subarray.
                            animNodesArray = Uint16Array.from(new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2));
                            break;
                        case 4:
                            // NB: A *copy* of the subarray.
                            animNodesArray = Uint32Array.from(new Uint32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4));
                            break;
                    }
                }
            }
            featureTable = {
                multiModel: false,
                data: packedFeatureArray,
                numFeatures: header.count,
                animationNodeIds: animNodesArray,
            };
        }
        this._stream.curPos = this._featureTableInfo.startPos + header.length;
        return featureTable;
    }
    parseNodes(featureTable) {
        const nodes = [];
        const docNodes = this._document.nodes;
        const docMeshes = this._document.meshes;
        if (undefined === docNodes.Node_Root) {
            // A veeeery early version of the tile format (prior to introduction of schedule animation support) just supplied a flat list of meshes.
            // We shall never encounter such tiles again.
            return nodes;
        }
        for (const nodeKey of Object.keys(docNodes)) {
            const docNode = this._document.nodes[nodeKey];
            (0, core_bentley_1.assert)(undefined !== docNode); // we're iterating the keys...
            const docMesh = docMeshes[docNode];
            const docPrimitives = docMesh?.primitives;
            if (!docPrimitives)
                continue;
            const layerId = docMesh.layer;
            if ("Node_Root" === nodeKey) {
                if (this._timeline) {
                    // Split up the root node into transform nodes.
                    this.parseAnimationBranches(nodes, docMesh, featureTable, this._timeline);
                }
                else if (this._options.createUntransformedRootNode) {
                    // If transform nodes exist in the tile tree, then we need to create a branch for the root node so that elements not associated with
                    // any node in the schedule script can be grouped together.
                    nodes.push({
                        animationNodeId: AnimationNodeId_1.AnimationNodeId.Untransformed,
                        primitives: this.parseNodePrimitives(docPrimitives),
                    });
                }
                else {
                    nodes.push({ primitives: this.parseNodePrimitives(docPrimitives) });
                }
            }
            else if (undefined === layerId) {
                nodes.push({
                    animationNodeId: extractNodeId(nodeKey),
                    animationId: `${this._options.batchModelId}_${nodeKey}`,
                    primitives: this.parseNodePrimitives(docPrimitives),
                });
            }
            else {
                nodes.push({
                    layerId,
                    primitives: this.parseNodePrimitives(docPrimitives),
                });
            }
        }
        return nodes;
    }
    parseAnimationBranches(output, docMesh, imdlFeatureTable, timeline) {
        const docPrimitives = docMesh.primitives;
        if (!docPrimitives)
            return;
        const primitives = docPrimitives.map((x) => this.parseNodePrimitive(x)).filter((x) => x !== undefined);
        if (primitives.length === 0)
            return;
        const nodesById = new Map();
        const getNode = (nodeId) => {
            nodeId = nodeId ?? AnimationNodeId_1.AnimationNodeId.Untransformed;
            let node = nodesById.get(nodeId);
            if (!node) {
                node = {
                    animationNodeId: nodeId,
                    animationId: `${this._options.batchModelId}_Node_${nodeId}`,
                    primitives: [],
                };
                nodesById.set(nodeId, node);
                output.push(node);
            }
            return node;
        };
        // NB: The BatchType is irrelevant - just use Primary.
        (0, core_bentley_1.assert)(undefined === imdlFeatureTable.animationNodeIds);
        const featureTable = convertFeatureTable(imdlFeatureTable, this._options.batchModelId);
        featureTable.populateAnimationNodeIds((feature) => timeline.getBatchIdForFeature(feature), timeline.maxBatchId);
        imdlFeatureTable.animationNodeIds = featureTable.animationNodeIds;
        const discreteNodeIds = timeline.discreteBatchIds;
        const computeNodeId = (featureIndex) => {
            const nodeId = featureTable.getAnimationNodeId(featureIndex);
            return 0 !== nodeId && discreteNodeIds.has(nodeId) ? nodeId : 0;
        };
        this.splitPrimitives(primitives, featureTable, computeNodeId, getNode);
    }
    splitPrimitives(primitives, featureTable, computeNodeId, getPrimitivesNode) {
        const splitArgs = {
            maxDimension: this._options.maxVertexTableSize,
            computeNodeId,
            featureTable,
        };
        const convertMaterial = (imdl) => {
            if (!imdl)
                return undefined;
            else if (imdl.isAtlas)
                return imdl;
            const material = (typeof imdl.material === "string") ? this.materialFromJson(imdl.material) : Material.create(toMaterialParams(imdl.material));
            return material ? { isAtlas: false, material } : undefined;
        };
        for (const primitive of primitives) {
            switch (primitive.type) {
                case "pattern": {
                    // ###TODO splitting area patterns
                    getPrimitivesNode(undefined).primitives.push(primitive);
                    break;
                }
                case "mesh": {
                    const mesh = primitive.params;
                    const texMap = mesh.surface.textureMapping;
                    const params = {
                        vertices: toVertexTable(primitive.params.vertices),
                        surface: {
                            ...primitive.params.surface,
                            indices: new VertexIndices_1.VertexIndices(primitive.params.surface.indices),
                            material: convertMaterial(mesh.surface.material),
                            textureMapping: texMap ? {
                                alwaysDisplayed: texMap.alwaysDisplayed,
                                // The texture type doesn't actually matter here.
                                texture: typeof texMap.texture === "string" ? new NamedTexture(texMap.texture, core_common_1.RenderTexture.Type.Normal) : new GradientTexture(texMap.texture),
                            } : undefined,
                        },
                        edges: primitive.params.edges ? edgeParamsFromImdl(primitive.params.edges) : undefined,
                        isPlanar: primitive.params.isPlanar,
                        auxChannels: primitive.params.auxChannels ? AuxChannelTable_1.AuxChannelTable.fromJSON(primitive.params.auxChannels) : undefined,
                    };
                    const split = (0, VertexTableSplitter_1.splitMeshParams)({
                        ...splitArgs,
                        params,
                        createMaterial: (args) => Material.create(args),
                    });
                    for (const [nodeId, p] of split) {
                        let material;
                        if (p.surface.material) {
                            if (p.surface.material.isAtlas) {
                                material = p.surface.material;
                            }
                            else {
                                (0, core_bentley_1.assert)(p.surface.material.material instanceof Material);
                                material = p.surface.material.material.toImdl();
                            }
                        }
                        (0, core_bentley_1.assert)(p.surface.textureMapping === undefined || p.surface.textureMapping.texture instanceof Texture);
                        getPrimitivesNode(nodeId).primitives.push({
                            type: "mesh",
                            modifier: primitive.modifier,
                            params: {
                                vertices: fromVertexTable(p.vertices),
                                surface: {
                                    ...p.surface,
                                    indices: p.surface.indices.data,
                                    material,
                                    textureMapping: p.surface.textureMapping?.texture instanceof Texture ? {
                                        texture: p.surface.textureMapping.texture.toImdl(),
                                        alwaysDisplayed: p.surface.textureMapping.alwaysDisplayed,
                                    } : undefined,
                                },
                                edges: p.edges ? edgeParamsToImdl(p.edges) : undefined,
                                isPlanar: p.isPlanar,
                                auxChannels: p.auxChannels?.toJSON(),
                            },
                        });
                    }
                    break;
                }
                case "point": {
                    const params = {
                        vertices: toVertexTable(primitive.params.vertices),
                        indices: new VertexIndices_1.VertexIndices(primitive.params.indices),
                        weight: primitive.params.weight,
                    };
                    const split = (0, VertexTableSplitter_1.splitPointStringParams)({ ...splitArgs, params });
                    for (const [nodeId, p] of split) {
                        getPrimitivesNode(nodeId).primitives.push({
                            type: "point",
                            modifier: primitive.modifier,
                            params: {
                                vertices: fromVertexTable(p.vertices),
                                indices: p.indices.data,
                                weight: p.weight,
                            },
                        });
                    }
                    break;
                }
                case "polyline": {
                    const params = {
                        ...primitive.params,
                        vertices: toVertexTable(primitive.params.vertices),
                        polyline: {
                            indices: new VertexIndices_1.VertexIndices(primitive.params.polyline.indices),
                            prevIndices: new VertexIndices_1.VertexIndices(primitive.params.polyline.prevIndices),
                            nextIndicesAndParams: primitive.params.polyline.nextIndicesAndParams,
                        },
                    };
                    const split = (0, VertexTableSplitter_1.splitPolylineParams)({ ...splitArgs, params });
                    for (const [nodeId, p] of split) {
                        getPrimitivesNode(nodeId).primitives.push({
                            type: "polyline",
                            modifier: primitive.modifier,
                            params: {
                                ...p,
                                vertices: fromVertexTable(p.vertices),
                                polyline: {
                                    indices: p.polyline.indices.data,
                                    prevIndices: p.polyline.prevIndices.data,
                                    nextIndicesAndParams: p.polyline.nextIndicesAndParams,
                                },
                            },
                        });
                    }
                    break;
                }
            }
        }
    }
    groupPrimitiveNodes(inputNodes, imdlFeatureTable) {
        const modelGroups = this._options.modelGroups;
        if (!modelGroups?.length)
            return inputNodes;
        const groupNodes = [];
        let orphanNode;
        const getGroupNode = (groupId) => {
            (0, core_bentley_1.assert)(groupId <= modelGroups.length);
            if (groupId === modelGroups.length) {
                // This would happen if:
                //  - The tile contains geometry from a model not present in modelGroups (should never occur); or
                //  - The tile contains an area pattern (we haven't yet implemented splitting for them).
                // In either case, orphaned geometry will end up getting discarded.
                return orphanNode ?? (orphanNode = { groupId, nodes: [] });
            }
            let groupNode = groupNodes[groupId];
            if (!groupNode)
                groupNodes[groupId] = groupNode = { groupId, nodes: [] };
            return groupNode;
        };
        const featureTable = convertFeatureTable(imdlFeatureTable, this._options.batchModelId);
        const modelIdPair = { lower: 0, upper: 0 };
        const computeNodeId = (featureIndex) => {
            featureTable.getModelIdPair(featureIndex, modelIdPair);
            const modelId = core_bentley_1.Id64.fromUint32PairObject(modelIdPair);
            for (let i = 0; i < modelGroups.length; i++) {
                if (modelGroups[i].has(modelId))
                    return i;
            }
            return modelGroups.length;
        };
        for (const inputNode of inputNodes) {
            // Indexed by model group index.
            const splitNodes = [];
            const getSplitNode = (groupIndex) => {
                groupIndex = groupIndex ?? modelGroups.length;
                if (!splitNodes[groupIndex]) {
                    const splitNode = splitNodes[groupIndex] = { ...inputNode, primitives: [] };
                    getGroupNode(groupIndex).nodes.push(splitNode);
                }
                return splitNodes[groupIndex];
            };
            this.splitPrimitives(inputNode.primitives, featureTable, computeNodeId, getSplitNode);
        }
        return groupNodes.filter((x) => undefined !== x);
    }
    parseTesselatedPolyline(json) {
        const indices = this.findBuffer(json.indices);
        const prevIndices = this.findBuffer(json.prevIndices);
        const nextIndicesAndParams = this.findBuffer(json.nextIndicesAndParams);
        return indices && prevIndices && nextIndicesAndParams ? { indices, prevIndices, nextIndicesAndParams } : undefined;
    }
    parseSegmentEdges(imdl) {
        const indices = this.findBuffer(imdl.indices);
        const endPointAndQuadIndices = this.findBuffer(imdl.endPointAndQuadIndices);
        return indices && endPointAndQuadIndices ? { indices, endPointAndQuadIndices } : undefined;
    }
    parseSilhouetteEdges(imdl) {
        const segments = this.parseSegmentEdges(imdl);
        const normalPairs = this.findBuffer(imdl.normalPairs);
        return segments && normalPairs ? { ...segments, normalPairs } : undefined;
    }
    parseIndexedEdges(imdl) {
        const indices = this.findBuffer(imdl.indices);
        const edgeTable = this.findBuffer(imdl.edges);
        if (!indices || !edgeTable)
            return undefined;
        return {
            indices,
            edges: {
                data: edgeTable,
                width: imdl.width,
                height: imdl.height,
                silhouettePadding: imdl.silhouettePadding,
                numSegments: imdl.numSegments,
            },
        };
    }
    parseCompactEdges(imdl, vertexIndices) {
        const visibility = this.findBuffer(imdl.visibility);
        if (!visibility)
            return undefined;
        const normals = undefined !== imdl.normalPairs ? this.findBuffer(imdl.normalPairs) : undefined;
        return (0, CompactEdges_1.indexedEdgeParamsFromCompactEdges)({
            numVisibleEdges: imdl.numVisible,
            visibility,
            vertexIndices,
            normalPairs: normals ? new Uint32Array(normals.buffer, normals.byteOffset, normals.byteLength / 4) : undefined,
            maxEdgeTableDimension: this._options.maxVertexTableSize,
        });
    }
    parseEdges(imdl, displayParams, indices) {
        if (!imdl)
            return undefined;
        const segments = imdl.segments ? this.parseSegmentEdges(imdl.segments) : undefined;
        const silhouettes = imdl.silhouettes ? this.parseSilhouetteEdges(imdl.silhouettes) : undefined;
        const polylines = imdl.polylines ? this.parseTesselatedPolyline(imdl.polylines) : undefined;
        let indexed = imdl.indexed ? this.parseIndexedEdges(imdl.indexed) : undefined;
        if (!indexed && imdl.compact)
            indexed = this.parseCompactEdges(imdl.compact, new VertexIndices_1.VertexIndices(indices));
        if (!segments && !silhouettes && !indexed && !polylines)
            return undefined;
        return {
            segments,
            silhouettes,
            polylines,
            indexed,
            weight: displayParams.width,
            linePixels: displayParams.linePixels,
        };
    }
    getPattern(name) {
        let primitives = this._patterns.get(name);
        if (!primitives) {
            const symbol = this._document.patternSymbols[name];
            primitives = symbol ? this.parsePrimitives(symbol.primitives) : [];
            this._patterns.set(name, primitives);
        }
        return primitives.length > 0 ? primitives : undefined;
    }
    parseAreaPattern(json) {
        const primitives = this.getPattern(json.symbolName);
        if (!primitives || primitives.length === 0)
            return undefined;
        const xyOffsets = this.findBuffer(json.xyOffsets);
        if (!xyOffsets)
            return undefined;
        return {
            type: "pattern",
            params: {
                ...json,
                xyOffsets: new Float32Array(xyOffsets.buffer, xyOffsets.byteOffset, xyOffsets.byteLength / 4),
            },
        };
    }
    parseNodePrimitives(docPrimitives) {
        const primitives = [];
        for (const docPrimitive of docPrimitives) {
            const primitive = this.parseNodePrimitive(docPrimitive);
            if (primitive)
                primitives.push(primitive);
        }
        return primitives;
    }
    parseNodePrimitive(docPrimitive) {
        return docPrimitive.type === "areaPattern" ? this.parseAreaPattern(docPrimitive) : this.parsePrimitive(docPrimitive);
    }
    parsePrimitives(docPrimitives) {
        const primitives = [];
        for (const docPrimitive of docPrimitives) {
            const primitive = this.parsePrimitive(docPrimitive);
            if (primitive)
                primitives.push(primitive);
        }
        return primitives;
    }
    parsePrimitive(docPrimitive) {
        let modifier = this.parseInstances(docPrimitive);
        if (!modifier && docPrimitive.viewIndependentOrigin) {
            const origin = core_geometry_1.Point3d.fromJSON(docPrimitive.viewIndependentOrigin);
            modifier = {
                type: "viewIndependentOrigin",
                origin: { x: origin.x, y: origin.y, z: origin.z },
            };
        }
        const materialName = docPrimitive.material ?? "";
        const dpMaterial = materialName.length ? core_bentley_1.JsonUtils.asObject(this._document.materials[materialName]) : undefined;
        const displayParams = dpMaterial ? this.parseDisplayParams(dpMaterial) : undefined;
        if (!displayParams)
            return undefined;
        const vertices = this.parseVertexTable(docPrimitive);
        if (!vertices)
            return undefined;
        let primitive;
        const isPlanar = !this._options.is3d || core_bentley_1.JsonUtils.asBool(docPrimitive.isPlanar);
        switch (docPrimitive.type) {
            case MeshPrimitive_1.MeshPrimitiveType.Mesh: {
                const surface = this.parseSurface(docPrimitive, displayParams);
                if (surface) {
                    primitive = {
                        type: "mesh",
                        params: {
                            vertices,
                            surface,
                            isPlanar,
                            auxChannels: this.parseAuxChannelTable(docPrimitive),
                            edges: this.parseEdges(docPrimitive.edges, displayParams, surface.indices),
                        },
                    };
                }
                break;
            }
            case MeshPrimitive_1.MeshPrimitiveType.Polyline: {
                const polyline = this.parseTesselatedPolyline(docPrimitive);
                if (polyline) {
                    let type = core_common_1.PolylineTypeFlags.Normal;
                    if (DisplayParams_1.DisplayParams.RegionEdgeType.Outline === displayParams.regionEdgeType)
                        type = (!displayParams.gradient || displayParams.gradient.isOutlined) ? core_common_1.PolylineTypeFlags.Edge : core_common_1.PolylineTypeFlags.Outline;
                    primitive = {
                        type: "polyline",
                        params: {
                            vertices,
                            polyline,
                            isPlanar,
                            type,
                            weight: displayParams.width,
                            linePixels: displayParams.linePixels,
                        },
                    };
                }
                break;
            }
            case MeshPrimitive_1.MeshPrimitiveType.Point: {
                const indices = this.findBuffer(docPrimitive.indices);
                const weight = displayParams.width;
                if (indices) {
                    primitive = {
                        type: "point",
                        params: { vertices, indices, weight },
                    };
                }
                break;
            }
        }
        if (primitive)
            primitive.modifier = modifier;
        return primitive;
    }
    parseSurface(mesh, displayParams) {
        const surf = mesh.surface;
        if (!surf)
            return undefined;
        const indices = this.findBuffer(surf.indices);
        if (!indices)
            return undefined;
        const type = surf.type;
        if (!(0, SurfaceParams_1.isValidSurfaceType)(type))
            return undefined;
        const texture = displayParams.textureMapping?.texture;
        let material;
        const atlas = mesh.vertices.materialAtlas;
        const numColors = mesh.vertices.numColors;
        if (atlas && undefined !== numColors) {
            material = {
                isAtlas: true,
                hasTranslucency: core_bentley_1.JsonUtils.asBool(atlas.hasTranslucency),
                overridesAlpha: core_bentley_1.JsonUtils.asBool(atlas.overridesAlpha, false),
                vertexTableOffset: core_bentley_1.JsonUtils.asInt(numColors),
                numMaterials: core_bentley_1.JsonUtils.asInt(atlas.numMaterials),
            };
        }
        else if (displayParams.material) {
            (0, core_bentley_1.assert)(displayParams.material instanceof Material);
            material = displayParams.material.toImdl();
        }
        let textureMapping;
        if (texture) {
            (0, core_bentley_1.assert)(texture instanceof Texture);
            textureMapping = {
                texture: texture.toImdl(),
                alwaysDisplayed: core_bentley_1.JsonUtils.asBool(surf.alwaysDisplayTexture),
            };
        }
        return {
            type,
            indices,
            fillFlags: displayParams.fillFlags,
            hasBakedLighting: false,
            material,
            textureMapping,
        };
    }
    parseAuxChannelTable(primitive) {
        const json = primitive.auxChannels;
        if (undefined === json)
            return undefined;
        const bytes = this.findBuffer(core_bentley_1.JsonUtils.asString(json.bufferView));
        if (undefined === bytes)
            return undefined;
        return {
            data: bytes,
            width: json.width,
            height: json.height,
            count: json.count,
            numBytesPerVertex: json.numBytesPerVertex,
            displacements: json.displacements,
            normals: json.normals,
            params: json.params,
        };
    }
    parseVertexTable(primitive) {
        const json = primitive.vertices;
        if (!json)
            return undefined;
        const bytes = this.findBuffer(core_bentley_1.JsonUtils.asString(json.bufferView));
        if (!bytes)
            return undefined;
        const uniformFeatureID = undefined !== json.featureID ? core_bentley_1.JsonUtils.asInt(json.featureID) : undefined;
        const rangeMin = core_bentley_1.JsonUtils.asArray(json.params.decodedMin);
        const rangeMax = core_bentley_1.JsonUtils.asArray(json.params.decodedMax);
        if (undefined === rangeMin || undefined === rangeMax)
            return undefined;
        const qparams = core_common_1.QParams3d.fromRange(core_geometry_1.Range3d.create(core_geometry_1.Point3d.create(rangeMin[0], rangeMin[1], rangeMin[2]), core_geometry_1.Point3d.create(rangeMax[0], rangeMax[1], rangeMax[2])));
        const uniformColor = undefined !== json.uniformColor ? core_common_1.ColorDef.fromJSON(json.uniformColor) : undefined;
        let uvParams;
        if (MeshPrimitive_1.MeshPrimitiveType.Mesh === primitive.type && primitive.surface && primitive.surface.uvParams) {
            const uvMin = primitive.surface.uvParams.decodedMin;
            const uvMax = primitive.surface.uvParams.decodedMax;
            const uvRange = new core_geometry_1.Range2d(uvMin[0], uvMin[1], uvMax[0], uvMax[1]);
            uvParams = core_common_1.QParams2d.fromRange(uvRange);
        }
        return {
            data: bytes,
            usesUnquantizedPositions: true === json.usesUnquantizedPositions,
            qparams: qparams.toJSON(),
            width: json.width,
            height: json.height,
            hasTranslucency: json.hasTranslucency,
            uniformColor: uniformColor?.toJSON(),
            featureIndexType: json.featureIndexType,
            uniformFeatureID,
            numVertices: json.count,
            numRgbaPerVertex: json.numRgbaPerVertex,
            uvParams: uvParams?.toJSON(),
        };
    }
    parseInstances(primitive) {
        const json = primitive.instances;
        if (!json)
            return undefined;
        const count = core_bentley_1.JsonUtils.asInt(json.count, 0);
        if (count <= 0)
            return undefined;
        const centerComponents = core_bentley_1.JsonUtils.asArray(json.transformCenter);
        if (undefined === centerComponents || 3 !== centerComponents.length)
            return undefined;
        const transformCenter = core_geometry_1.Point3d.create(centerComponents[0], centerComponents[1], centerComponents[2]);
        const featureIds = this.findBuffer(core_bentley_1.JsonUtils.asString(json.featureIds));
        if (undefined === featureIds)
            return undefined;
        const transformBytes = this.findBuffer(core_bentley_1.JsonUtils.asString(json.transforms));
        if (undefined === transformBytes)
            return undefined;
        // 1 transform = 3 rows of 4 floats = 12 floats per instance
        const numFloats = transformBytes.byteLength / 4;
        (0, core_bentley_1.assert)(Math.floor(numFloats) === numFloats);
        (0, core_bentley_1.assert)(0 === numFloats % 12);
        const transforms = new Float32Array(transformBytes.buffer, transformBytes.byteOffset, numFloats);
        let symbologyOverrides;
        if (undefined !== json.symbologyOverrides)
            symbologyOverrides = this.findBuffer(core_bentley_1.JsonUtils.asString(json.symbologyOverrides));
        return {
            type: "instances",
            count,
            transforms,
            transformCenter,
            featureIds,
            symbologyOverrides,
        };
    }
    findBuffer(bufferViewId) {
        if (typeof bufferViewId !== "string" || 0 === bufferViewId.length)
            return undefined;
        const bufferViewJson = this._document.bufferViews[bufferViewId];
        if (undefined === bufferViewJson)
            return undefined;
        const byteOffset = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteOffset);
        const byteLength = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteLength);
        if (0 === byteLength)
            return undefined;
        return this._binaryData.subarray(byteOffset, byteOffset + byteLength);
    }
    colorDefFromMaterialJson(json) {
        return undefined !== json ? core_common_1.ColorDef.from(json[0] * 255 + 0.5, json[1] * 255 + 0.5, json[2] * 255 + 0.5) : undefined;
    }
    materialFromJson(key) {
        const materialJson = this._document.renderMaterials[key];
        if (!materialJson)
            return undefined;
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        const materialParams = new core_common_1.RenderMaterial.Params(key);
        materialParams.diffuseColor = this.colorDefFromMaterialJson(materialJson.diffuseColor);
        if (materialJson.diffuse !== undefined)
            materialParams.diffuse = core_bentley_1.JsonUtils.asDouble(materialJson.diffuse);
        materialParams.specularColor = this.colorDefFromMaterialJson(materialJson.specularColor);
        if (materialJson.specular !== undefined)
            materialParams.specular = core_bentley_1.JsonUtils.asDouble(materialJson.specular);
        materialParams.reflectColor = this.colorDefFromMaterialJson(materialJson.reflectColor);
        if (materialJson.reflect !== undefined)
            materialParams.reflect = core_bentley_1.JsonUtils.asDouble(materialJson.reflect);
        if (materialJson.specularExponent !== undefined)
            materialParams.specularExponent = materialJson.specularExponent;
        if (undefined !== materialJson.transparency)
            materialParams.alpha = 1.0 - materialJson.transparency;
        materialParams.refract = core_bentley_1.JsonUtils.asDouble(materialJson.refract);
        materialParams.shadows = core_bentley_1.JsonUtils.asBool(materialJson.shadows);
        materialParams.ambient = core_bentley_1.JsonUtils.asDouble(materialJson.ambient);
        if (undefined !== materialJson.textureMapping)
            materialParams.textureMapping = this.textureMappingFromJson(materialJson.textureMapping.texture);
        return new Material(materialParams);
    }
    parseNamedTexture(namedTex, name) {
        const textureType = core_bentley_1.JsonUtils.asBool(namedTex.isGlyph) ? core_common_1.RenderTexture.Type.Glyph :
            (core_bentley_1.JsonUtils.asBool(namedTex.isTileSection) ? core_common_1.RenderTexture.Type.TileSection : core_common_1.RenderTexture.Type.Normal);
        return new NamedTexture(name, textureType);
    }
    parseConstantLodProps(propsJson) {
        if (undefined === propsJson)
            return undefined;
        return {
            repetitions: core_bentley_1.JsonUtils.asDouble(propsJson.repetitions, 1.0),
            offset: { x: propsJson.offset ? core_bentley_1.JsonUtils.asDouble(propsJson.offset[0]) : 0.0, y: propsJson.offset ? core_bentley_1.JsonUtils.asDouble(propsJson.offset[1]) : 0.0 },
            minDistClamp: core_bentley_1.JsonUtils.asDouble(propsJson.minDistClamp, 1.0),
            maxDistClamp: core_bentley_1.JsonUtils.asDouble(propsJson.maxDistClamp, 4096.0 * 1024.0 * 1024.0),
        };
    }
    textureMappingFromJson(json) {
        if (!json)
            return undefined;
        const name = core_bentley_1.JsonUtils.asString(json.name);
        const namedTex = 0 !== name.length ? this._document.namedTextures[name] : undefined;
        const texture = namedTex ? this.parseNamedTexture(namedTex, name) : undefined;
        if (!texture)
            return undefined;
        const paramsJson = json.params;
        const tf = paramsJson.transform;
        const paramProps = {
            textureMat2x3: new core_common_1.TextureMapping.Trans2x3(tf[0][0], tf[0][1], tf[0][2], tf[1][0], tf[1][1], tf[1][2]),
            textureWeight: core_bentley_1.JsonUtils.asDouble(paramsJson.weight, 1.0),
            mapMode: core_bentley_1.JsonUtils.asInt(paramsJson.mode),
            worldMapping: core_bentley_1.JsonUtils.asBool(paramsJson.worldMapping),
            useConstantLod: core_bentley_1.JsonUtils.asBool(paramsJson.useConstantLod),
            constantLodProps: this.parseConstantLodProps(paramsJson.constantLodParams),
        };
        const textureMapping = new core_common_1.TextureMapping(texture, new core_common_1.TextureMapping.Params(paramProps));
        const normalMapJson = json.normalMapParams;
        if (normalMapJson) {
            const normalTexName = core_bentley_1.JsonUtils.asString(normalMapJson.textureName);
            const namedNormalTex = normalTexName.length > 0 ? this._document.namedTextures[normalTexName] : undefined;
            const normalMap = namedNormalTex ? this.parseNamedTexture(namedNormalTex, normalTexName) : undefined;
            if (normalMap) {
                textureMapping.normalMapParams = {
                    normalMap,
                    greenUp: core_bentley_1.JsonUtils.asBool(normalMapJson.greenUp),
                    scale: core_bentley_1.JsonUtils.asDouble(normalMapJson.scale, 1),
                    useConstantLod: core_bentley_1.JsonUtils.asBool(normalMapJson.useConstantLod),
                };
            }
        }
        return textureMapping;
    }
    parseDisplayParams(json) {
        const type = core_bentley_1.JsonUtils.asInt(json.type, DisplayParams_1.DisplayParams.Type.Mesh);
        const lineColor = core_common_1.ColorDef.create(core_bentley_1.JsonUtils.asInt(json.lineColor));
        const fillColor = core_common_1.ColorDef.create(core_bentley_1.JsonUtils.asInt(json.fillColor));
        const width = core_bentley_1.JsonUtils.asInt(json.lineWidth);
        const linePixels = core_bentley_1.JsonUtils.asInt(json.linePixels, core_common_1.LinePixels.Solid);
        const fillFlags = core_bentley_1.JsonUtils.asInt(json.fillFlags, core_common_1.FillFlags.None);
        const ignoreLighting = core_bentley_1.JsonUtils.asBool(json.ignoreLighting);
        // Material will always contain its own texture if it has one
        const materialKey = json.materialId;
        const material = undefined !== materialKey ? this.materialFromJson(materialKey) : undefined;
        // We will only attempt to include the texture if material is undefined
        let textureMapping;
        let gradient;
        if (!material) {
            const textureJson = json.texture;
            textureMapping = undefined !== textureJson ? this.textureMappingFromJson(textureJson) : undefined;
            if (undefined === textureMapping) {
                const gradientProps = json.gradient;
                gradient = undefined !== gradientProps ? core_common_1.Gradient.Symb.fromJSON(gradientProps) : undefined;
                if (gradient) {
                    (0, core_bentley_1.assert)(undefined !== gradientProps);
                    const texture = new GradientTexture(gradientProps);
                    textureMapping = new core_common_1.TextureMapping(texture, new core_common_1.TextureMapping.Params({ textureMat2x3: new core_common_1.TextureMapping.Trans2x3(0, 1, 0, 1, 0, 0) }));
                }
            }
        }
        return new DisplayParams_1.DisplayParams(type, lineColor, fillColor, width, linePixels, fillFlags, material, gradient, ignoreLighting, textureMapping);
    }
}
/** @internal */
function toMaterialParams(mat) {
    const args = { alpha: mat.alpha };
    if (mat.diffuse) {
        args.diffuse = {
            weight: mat.diffuse.weight,
            color: undefined !== mat.diffuse.color ? core_common_1.ColorDef.fromJSON(mat.diffuse.color) : undefined,
        };
    }
    if (mat.specular) {
        args.specular = {
            weight: mat.specular.weight,
            exponent: mat.specular.exponent,
            color: undefined !== mat.specular.color ? core_common_1.ColorDef.fromJSON(mat.specular.color) : undefined,
        };
    }
    return args;
}
/** @internal */
function convertFeatureTable(imdlFeatureTable, batchModelId) {
    const table = imdlFeatureTable.multiModel
        ? core_common_1.MultiModelPackedFeatureTable.create(imdlFeatureTable.data, batchModelId, imdlFeatureTable.numFeatures, core_common_1.BatchType.Primary, imdlFeatureTable.numSubCategories)
        : new core_common_1.PackedFeatureTable(imdlFeatureTable.data, batchModelId, imdlFeatureTable.numFeatures, core_common_1.BatchType.Primary);
    table.animationNodeIds = imdlFeatureTable.animationNodeIds;
    return table;
}
/** @internal */
function parseImdlDocument(options) {
    const stream = core_bentley_1.ByteStream.fromUint8Array(options.data);
    const imdlHeader = new core_common_1.ImdlHeader(stream);
    if (!imdlHeader.isValid)
        return core_common_1.TileReadStatus.InvalidHeader;
    else if (!imdlHeader.isReadableVersion)
        return core_common_1.TileReadStatus.NewerMajorVersion;
    // Skip the feature table - we need to parse the JSON segment first to access its animationNodeIds.
    const ftStartPos = stream.curPos;
    const ftHeader = core_common_1.FeatureTableHeader.readFrom(stream);
    if (!ftHeader)
        return core_common_1.TileReadStatus.InvalidFeatureTable;
    stream.curPos = ftStartPos + ftHeader.length;
    // A glTF header follows the feature table
    const gltfHeader = new GltfHeader(stream);
    if (!gltfHeader.isValid)
        return core_common_1.TileReadStatus.InvalidTileData;
    stream.curPos = gltfHeader.scenePosition;
    const sceneStrData = stream.nextBytes(gltfHeader.sceneStrLength);
    const sceneStr = (0, core_bentley_1.utf8ToString)(sceneStrData);
    if (!sceneStr)
        return core_common_1.TileReadStatus.InvalidScene;
    try {
        const sceneValue = JSON.parse(sceneStr);
        const imdlDoc = {
            scene: core_bentley_1.JsonUtils.asString(sceneValue.scene),
            scenes: core_bentley_1.JsonUtils.asArray(sceneValue.scenes),
            animationNodes: core_bentley_1.JsonUtils.asObject(sceneValue.animationNodes),
            bufferViews: core_bentley_1.JsonUtils.asObject(sceneValue.bufferViews) ?? {},
            meshes: core_bentley_1.JsonUtils.asObject(sceneValue.meshes),
            nodes: core_bentley_1.JsonUtils.asObject(sceneValue.nodes) ?? {},
            materials: core_bentley_1.JsonUtils.asObject(sceneValue.materials) ?? {},
            renderMaterials: core_bentley_1.JsonUtils.asObject(sceneValue.renderMaterials) ?? {},
            namedTextures: core_bentley_1.JsonUtils.asObject(sceneValue.namedTextures) ?? {},
            patternSymbols: core_bentley_1.JsonUtils.asObject(sceneValue.patternSymbols) ?? {},
            rtcCenter: core_bentley_1.JsonUtils.asArray(sceneValue.rtcCenter),
        };
        if (!imdlDoc.meshes)
            return core_common_1.TileReadStatus.InvalidTileData;
        const binaryData = new Uint8Array(stream.arrayBuffer, gltfHeader.binaryPosition);
        const featureTable = {
            startPos: ftStartPos,
            multiModel: 0 !== (imdlHeader.flags & core_common_1.ImdlFlags.MultiModelFeatureTable),
        };
        const parser = new Parser(imdlDoc, binaryData, options, featureTable, stream);
        return parser.parse();
    }
    catch {
        return core_common_1.TileReadStatus.InvalidTileData;
    }
}
//# sourceMappingURL=ParseImdlDocument.js.map