"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.B3dmReader = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const GltfSchema_1 = require("../../common/gltf/GltfSchema");
const internal_1 = require("../../tile/internal");
/**
 * Deserializes a tile in [b3dm](https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification/TileFormats/Batched3DModel) format.
 */
class B3dmReader extends internal_1.GltfReader {
    _range;
    _isLeaf;
    _batchTableLength;
    _transformToRoot;
    _batchTableJson;
    _pseudoRtcBias;
    _batchIdRemap = new Map();
    _colors;
    _modelId;
    static create(stream, iModel, modelId, is3d, range, system, yAxisUp, isLeaf, tileCenter, transformToRoot, isCanceled, idMap, deduplicateVertices = false, tileData) {
        const header = new core_common_1.B3dmHeader(stream);
        if (!header.isValid)
            return undefined;
        let returnToCenterTransform, pseudoRtcBias;
        if (header.featureTableJson && Array.isArray(header.featureTableJson.RTC_CENTER)) {
            returnToCenterTransform = core_geometry_1.Transform.createTranslationXYZ(header.featureTableJson.RTC_CENTER[0], header.featureTableJson.RTC_CENTER[1], header.featureTableJson.RTC_CENTER[2]);
        }
        else {
            /**
             * This is a workaround for tiles generated by
             * context capture which have a large offset from the tileset origin that exceeds the
             * capacity of 32 bit integers. It is essentially an ad hoc RTC applied at read time only if the tile is far from the
             * origin and there is no RTC supplied either with the B3DM of the GLTF.
             * as the vertices are supplied in a quantized format, applying the RTC bias to
             * quantization origin will make these tiles work correctly.
             */
            pseudoRtcBias = core_geometry_1.Vector3d.create(tileCenter.x, tileCenter.y, tileCenter.z);
        }
        if (undefined !== returnToCenterTransform)
            transformToRoot = transformToRoot ? transformToRoot.multiplyTransformTransform(returnToCenterTransform) : returnToCenterTransform;
        const props = internal_1.GltfReaderProps.create(stream.nextBytes(header.length - stream.curPos), yAxisUp);
        const batchTableLength = header.featureTableJson ? core_bentley_1.JsonUtils.asInt(header.featureTableJson.BATCH_LENGTH, 0) : 0;
        return undefined !== props ? new B3dmReader(props, iModel, modelId, is3d, system, range, isLeaf, batchTableLength, transformToRoot, header.batchTableJson, isCanceled, idMap, pseudoRtcBias, deduplicateVertices, tileData) : undefined;
    }
    constructor(props, iModel, modelId, is3d, system, _range, _isLeaf, _batchTableLength, _transformToRoot, _batchTableJson, shouldAbort, _idMap, _pseudoRtcBias, deduplicateVertices = false, tileData) {
        super({
            props, iModel, system, shouldAbort, deduplicateVertices,
            is2d: !is3d, idMap: _idMap, tileData
        });
        this._range = _range;
        this._isLeaf = _isLeaf;
        this._batchTableLength = _batchTableLength;
        this._transformToRoot = _transformToRoot;
        this._batchTableJson = _batchTableJson;
        this._pseudoRtcBias = _pseudoRtcBias;
        this._modelId = modelId;
    }
    async read() {
        // NB: For reality models with no batch table, we want the model ID in the feature table
        const featureTable = new core_common_1.FeatureTable(this._batchTableLength ? this._batchTableLength : 1, this._modelId, this._type);
        if (this._batchTableLength > 0 && this._idMap !== undefined && this._batchTableJson !== undefined) {
            if (this._batchTableJson.extensions && this._batchTableJson.extensions["3DTILES_batch_table_hierarchy"]) {
                const hierarchy = this._batchTableJson.extensions["3DTILES_batch_table_hierarchy"];
                const { classIds, classes, parentIds, parentCounts, instancesLength } = hierarchy;
                if (classes !== undefined && classIds !== undefined && instancesLength !== 0) {
                    const classCounts = new Array(classes.length);
                    classCounts.fill(0);
                    const classIndexes = new Uint16Array(instancesLength);
                    for (let i = 0; i < instancesLength; ++i) {
                        const classId = classIds[i];
                        classIndexes[i] = classCounts[classId]++;
                    }
                    let parentMap;
                    if (parentIds) {
                        parentMap = new Array();
                        for (let i = 0, parentIndex = 0; i < instancesLength; i++) {
                            const parentCount = parentCounts === undefined ? 1 : parentCounts[i];
                            parentMap[i] = parentIds.slice(parentIndex, parentIndex += parentCount);
                        }
                    }
                    const getProperties = (instance, instanceIndex) => {
                        const classId = classIds[instanceIndex];
                        const instanceClass = classes[classId];
                        const instances = instanceClass.instances;
                        const indexInClass = classIndexes[instanceIndex];
                        for (const key in instances) { // eslint-disable-line guard-for-in
                            const value = instances[key][indexInClass];
                            if (value !== undefined && value !== null)
                                instance[key] = value;
                        }
                        if (parentIds !== undefined) {
                            const thisParents = (0, core_bentley_1.expectDefined)(parentMap)[instanceIndex];
                            for (const parentId of thisParents) {
                                if (parentId !== instanceIndex)
                                    getProperties(instance, parentId);
                            }
                        }
                    };
                    for (let batchId = 0; batchId < instancesLength; batchId++) {
                        const instance = {};
                        getProperties(instance, batchId);
                        this._batchIdRemap.set(batchId, featureTable.insert(new core_common_1.Feature(this._idMap.getBatchId(instance))));
                        const cesiumColor = instance["cesium#color"];
                        if (undefined !== cesiumColor) {
                            if (!this._colors) {
                                this._colors = new Array(instancesLength);
                                this._colors.fill(core_common_1.ColorDef.white.tbgr);
                            }
                            this._colors[batchId] = core_common_1.ColorDef.create(cesiumColor).tbgr;
                        }
                    }
                }
            }
            else {
                for (let i = 0; i < this._batchTableLength; i++) {
                    const feature = {};
                    for (const key in this._batchTableJson) // eslint-disable-line guard-for-in
                        feature[key] = this._batchTableJson[key][i];
                    this._batchIdRemap.set(i, featureTable.insert(new core_common_1.Feature(this._idMap.getBatchId(feature))));
                }
            }
        }
        if (featureTable.isEmpty) {
            this._batchIdRemap.set(0, 0);
            const feature = new core_common_1.Feature(this._modelId);
            featureTable.insert(feature);
        }
        await this.resolveResources();
        if (this._isCanceled)
            return { readStatus: core_common_1.TileReadStatus.Canceled, isLeaf: this._isLeaf };
        return this.readGltfAndCreateGraphics(this._isLeaf, featureTable, this._range, this._transformToRoot, this._pseudoRtcBias, undefined);
    }
    readBatchTable(mesh, json) {
        if (mesh.features !== undefined) {
            if (this._batchTableLength > 0 && undefined !== this._batchTableJson && undefined !== json.attributes) {
                const view = this.getBufferView(json.attributes, "_BATCHID");
                let batchIds;
                if (undefined !== view && (undefined !== (batchIds = view.toBufferData(GltfSchema_1.GltfDataType.UInt32)) || undefined !== (batchIds = view.toBufferData(GltfSchema_1.GltfDataType.Float)))) {
                    const indices = [];
                    const { colors, colorMap } = mesh;
                    let colorRemap;
                    if (this._colors && this._colors.length === this._batchTableLength) {
                        colorRemap = new Uint32Array(this._batchTableLength);
                        for (let i = 0; i < this._batchTableLength; i++)
                            colorRemap[i] = colorMap.insert(this._colors[i]);
                    }
                    for (let i = 0; i < batchIds.count; i++) {
                        const batchId = batchIds.buffer[i * view.stride];
                        const remapId = this._batchIdRemap.get(batchId);
                        indices.push(remapId === undefined ? 0 : remapId);
                        if (colorRemap)
                            colors.push(colorRemap[batchId]);
                    }
                    mesh.features.setIndices(indices);
                }
            }
            else {
                mesh.features.add(new core_common_1.Feature(this._modelId), 1);
            }
        }
    }
}
exports.B3dmReader = B3dmReader;
//# sourceMappingURL=B3dmReader.js.map