"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
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseGltf = 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 FrontendLoggerCategory_1 = require("../FrontendLoggerCategory");
const ImageUtil_1 = require("../ImageUtil");
const GltfSchema_1 = require("./GltfSchema");
/** Parse a [[GltfDocument]] or binary representation thereof to produce a [[Gltf.Model]].
 * This implementation is incomplete and not currently used.
 * @internal
 */
async function parseGltf(args) {
    const source = args.gltf;
    let version;
    let json;
    let binary;
    if (source instanceof Uint8Array) {
        // It may be JSON - check for magic indicating glb.
        const buffer = core_bentley_1.ByteStream.fromUint8Array(source);
        if (core_common_1.TileFormat.Gltf !== buffer.readUint32()) {
            try {
                const utf8Json = (0, core_bentley_1.utf8ToString)(source);
                if (!utf8Json)
                    return undefined;
                json = JSON.parse(utf8Json);
                version = 2;
            }
            catch (_) {
                return undefined;
            }
        }
        else {
            buffer.reset();
            const header = new core_common_1.GlbHeader(buffer);
            if (!header.isValid)
                return undefined;
            version = header.version;
            if (header.binaryChunk)
                binary = new Uint8Array(source.buffer, source.byteOffset + header.binaryChunk.offset, header.binaryChunk.length);
            try {
                const jsonBytes = new Uint8Array(source.buffer, source.byteOffset + header.jsonChunk.offset, header.jsonChunk.length);
                const jsonStr = (0, core_bentley_1.utf8ToString)(jsonBytes);
                if (undefined === jsonStr)
                    return undefined;
                json = JSON.parse(jsonStr);
            }
            catch (_) {
                return undefined;
            }
        }
    }
    else {
        version = 2; // ###TODO verify against source.asset?.version
        json = source;
    }
    // asset is required in glTF 2, optional in glTF 1
    const asset = core_bentley_1.JsonUtils.asObject(json.asset);
    if (version === 2 && !asset)
        return undefined;
    const document = {
        asset,
        scene: core_bentley_1.JsonUtils.asString(json.scene),
        extensions: core_bentley_1.JsonUtils.asObject(json.extensions),
        extensionsUsed: core_bentley_1.JsonUtils.asArray(json.extensionsUsed),
        extensionsRequired: core_bentley_1.JsonUtils.asArray(json.extensionsRequired),
        accessors: core_bentley_1.JsonUtils.asObject(json.accessors),
        buffers: core_bentley_1.JsonUtils.asObject(json.buffers),
        bufferViews: core_bentley_1.JsonUtils.asObject(json.bufferViews),
        images: core_bentley_1.JsonUtils.asObject(json.images),
        materials: core_bentley_1.JsonUtils.asObject(json.materials),
        meshes: core_bentley_1.JsonUtils.asObject(json.meshes),
        nodes: core_bentley_1.JsonUtils.asObject(json.nodes),
        samplers: core_bentley_1.JsonUtils.asObject(json.samplers),
        scenes: core_bentley_1.JsonUtils.asObject(json.scenes),
        textures: core_bentley_1.JsonUtils.asObject(json.textures),
        techniques: core_bentley_1.JsonUtils.asObject(json.techniques),
    };
    if (!document.meshes)
        return undefined;
    const logger = args.logger ?? {
        log: (message, type) => {
            const category = `${FrontendLoggerCategory_1.FrontendLoggerCategory.Package}.gltf`;
            const fn = type === "error" ? "logError" : (type === "warning" ? "logWarning" : "logInfo");
            core_bentley_1.Logger[fn](category, message);
        },
    };
    const parser = new GltfParser({
        document,
        version,
        upAxis: args.upAxis ?? "y",
        binary,
        baseUrl: args.baseUrl,
        logger,
        isCanceled: () => args.isCanceled ?? false,
        imageFromImageSource: (args.noCreateImageBitmap ?
            async (imgSrc) => (0, ImageUtil_1.imageElementFromImageSource)(imgSrc) :
            async (imgSrc) => (0, ImageUtil_1.imageBitmapFromImageSource)(imgSrc)),
    });
    return parser.parse();
}
exports.parseGltf = parseGltf;
class GltfParser {
    constructor(options) {
        this._dracoMeshes = new Map();
        this._version = options.version;
        this._upAxis = options.upAxis;
        this._baseUrl = options.baseUrl;
        this._logger = options.logger;
        this._isCanceled = options.isCanceled;
        this._imageFromImageSource = options.imageFromImageSource;
        const emptyDict = {};
        const doc = options.document;
        this._buffers = doc.buffers ?? emptyDict;
        this._images = doc.images ?? emptyDict;
        this._nodes = doc.nodes ?? emptyDict;
        this._meshes = doc.meshes ?? emptyDict;
        this._bufferViews = doc.bufferViews ?? emptyDict;
        this._accessors = doc.accessors ?? emptyDict;
        if (options.binary) {
            const buffer = this._buffers[this._version === 2 ? 0 : "binary_glTF"];
            if (buffer && undefined === buffer.uri)
                buffer.resolvedBuffer = { data: options.binary };
        }
        let sceneNodes;
        if (doc.scenes && undefined !== doc.scene)
            sceneNodes = doc.scenes[doc.scene]?.nodes;
        this._sceneNodes = sceneNodes ?? Object.keys(this._nodes);
    }
    async parse() {
        // ###TODO_GLTF RTC_CENTER
        // ###TODO_GLTF pseudo-rtc bias (apply translation to each point at read time, for scalable mesh...)
        const toWorld = undefined;
        await this.resolveResources();
        if (this._isCanceled())
            return undefined;
        // ###TODO_GLTF compute content range (maybe do so elsewhere?)
        // I think spec says POSITION must specify min and max?
        const nodes = [];
        for (const nodeKey of this._sceneNodes) {
            const node = this._nodes[nodeKey];
            if (node)
                nodes.push(this.parseNode(node));
        }
        return {
            toWorld,
            nodes,
        };
    }
    parseNode(node) {
        const primitives = [];
        for (const meshId of (0, GltfSchema_1.getGltfNodeMeshIds)(node)) {
            const mesh = this._meshes[meshId];
            if (!mesh)
                continue;
            const parsedPrimitives = this.parsePrimitives(mesh);
            for (const primitive of parsedPrimitives)
                primitives.push(primitive);
        }
        let toParent;
        if (node.matrix) {
            const origin = core_geometry_1.Point3d.create(node.matrix[12], node.matrix[13], node.matrix[14]);
            const matrix = core_geometry_1.Matrix3d.createRowValues(node.matrix[0], node.matrix[4], node.matrix[8], node.matrix[1], node.matrix[5], node.matrix[9], node.matrix[2], node.matrix[6], node.matrix[10]);
            toParent = core_geometry_1.Transform.createOriginAndMatrix(origin, matrix);
        }
        else if (node.rotation || node.scale || node.translation) {
            // SPEC: To compose the local transformation matrix, TRS properties MUST be converted to matrices and postmultiplied in the T * R * S order;
            // first the scale is applied to the vertices, then the rotation, and then the translation.
            const scale = core_geometry_1.Transform.createRefs(undefined, node.scale ? core_geometry_1.Matrix3d.createScale(node.scale[0], node.scale[1], node.scale[2]) : core_geometry_1.Matrix3d.identity);
            const rot = core_geometry_1.Transform.createRefs(undefined, node.rotation ? core_geometry_1.Matrix3d.createFromQuaternion(core_geometry_1.Point4d.create(node.rotation[0], node.rotation[1], node.rotation[2], node.rotation[3])) : core_geometry_1.Matrix3d.identity);
            rot.matrix.transposeInPlace(); // See comment on Matrix3d.createFromQuaternion
            const trans = core_geometry_1.Transform.createTranslation(node.translation ? new core_geometry_1.Point3d(node.translation[0], node.translation[1], node.translation[2]) : core_geometry_1.Point3d.createZero());
            toParent = scale.multiplyTransformTransform(rot);
            trans.multiplyTransformTransform(toParent, toParent);
        }
        return {
            primitives,
            toParent,
        };
    }
    parsePrimitives(mesh) {
        const primitives = [];
        if (!mesh.primitives)
            return primitives;
        for (const primitive of mesh.primitives) {
            const parsedPrimitive = this.parsePrimitive(primitive);
            if (parsedPrimitive)
                primitives.push(parsedPrimitive);
        }
        return primitives;
    }
    parsePrimitive(primitive) {
        const meshMode = core_bentley_1.JsonUtils.asInt(primitive.mode, GltfSchema_1.GltfMeshMode.Triangles);
        switch (meshMode) {
            case GltfSchema_1.GltfMeshMode.TriangleStrip:
                return this.parseTrianglesPrimitive(primitive);
            default:
                // ###TODO_GLTF Make parser support all primitive types. Consumer can choose to do whatever with them.
                return undefined;
        }
    }
    parseTrianglesPrimitive(primitive) {
        const posId = primitive.attributes.POSITION;
        const pos = undefined !== posId ? this._accessors[posId] : undefined;
        if (!pos)
            return undefined;
        return undefined; // ###TODO_GLTF
    }
    traverseNodes(nodeIds) {
        return (0, GltfSchema_1.traverseGltfNodes)(nodeIds, this._nodes, new Set());
    }
    async resolveResources() {
        // Load any external images and buffers.
        await this._resolveResources();
        // If any meshes are draco-compressed, dynamically load the decoder module and then decode the meshes.
        const dracoMeshes = [];
        for (const node of this.traverseNodes(this._sceneNodes)) {
            for (const meshId of (0, GltfSchema_1.getGltfNodeMeshIds)(node)) {
                const mesh = this._meshes[meshId];
                if (mesh?.primitives)
                    for (const primitive of mesh.primitives)
                        if (primitive.extensions?.KHR_draco_mesh_compression)
                            dracoMeshes.push(primitive.extensions.KHR_draco_mesh_compression);
            }
        }
        if (dracoMeshes.length === 0)
            return;
        try {
            const dracoLoader = (await Promise.resolve().then(() => __importStar(require("@loaders.gl/draco")))).DracoLoader;
            await Promise.all(dracoMeshes.map(async (x) => this.decodeDracoMesh(x, dracoLoader)));
        }
        catch (err) {
            core_bentley_1.Logger.logWarning(FrontendLoggerCategory_1.FrontendLoggerCategory.Render, "Failed to decode draco-encoded glTF mesh");
            core_bentley_1.Logger.logException(FrontendLoggerCategory_1.FrontendLoggerCategory.Render, err);
        }
    }
    async _resolveResources() {
        // ###TODO traverse the scene nodes to find resources referenced by them, instead of resolving everything - some resources may not
        // be required for the scene.
        const promises = [];
        try {
            for (const buffer of (0, GltfSchema_1.gltfDictionaryIterator)(this._buffers))
                if (!buffer.resolvedBuffer)
                    promises.push(this.resolveBuffer(buffer));
            await Promise.all(promises);
            if (this._isCanceled())
                return;
            promises.length = 0;
            for (const image of (0, GltfSchema_1.gltfDictionaryIterator)(this._images))
                if (!image.resolvedImage)
                    promises.push(this.resolveImage(image));
            await Promise.all(promises);
        }
        catch (_) {
            // ###TODO_GLTF log
        }
    }
    resolveUrl(uri) {
        try {
            return new URL(uri, this._baseUrl).toString();
        }
        catch (_) {
            return undefined;
        }
    }
    async resolveBuffer(buffer) {
        if (buffer.resolvedBuffer || undefined === buffer.uri)
            return;
        try {
            const url = this.resolveUrl(buffer.uri);
            const response = url ? await fetch(url) : undefined;
            if (this._isCanceled())
                return;
            const data = await response?.arrayBuffer();
            if (this._isCanceled())
                return;
            if (data)
                buffer.resolvedBuffer = { data: new Uint8Array(data) };
        }
        catch (_) {
            //
        }
    }
    async resolveImage(image) {
        if (image.resolvedImage)
            return;
        const bvSrc = undefined !== image.bufferView ? image : image.extensions?.KHR_binary_glTF;
        if (undefined !== bvSrc?.bufferView) {
            const format = undefined !== bvSrc.mimeType ? (0, ImageUtil_1.getImageSourceFormatForMimeType)(bvSrc.mimeType) : undefined;
            const bufferView = this._bufferViews[bvSrc.bufferView];
            if (undefined === format || !bufferView || !bufferView.byteLength || bufferView.byteLength < 0)
                return;
            const bufferData = this._buffers[bufferView.buffer]?.resolvedBuffer?.data;
            if (!bufferData)
                return;
            const offset = bufferView.byteOffset ?? 0;
            const bytes = bufferData.subarray(offset, offset + bufferView.byteLength);
            try {
                const imageSource = new core_common_1.ImageSource(bytes, format);
                image.resolvedImage = await this._imageFromImageSource(imageSource);
            }
            catch (_) {
                //
            }
            return;
        }
        const url = undefined !== image.uri ? this.resolveUrl(image.uri) : undefined;
        if (undefined !== url)
            image.resolvedImage = await (0, ImageUtil_1.tryImageElementFromUrl)(url);
    }
    async decodeDracoMesh(ext, loader) {
        const bv = this._bufferViews[ext.bufferView];
        if (!bv || !bv.byteLength)
            return;
        let buf = this._buffers[bv.buffer]?.resolvedBuffer?.data;
        if (!buf)
            return;
        const offset = bv.byteOffset ?? 0;
        buf = buf.subarray(offset, offset + bv.byteLength);
        const mesh = await loader.parse(buf, {}); // NB: `options` argument declared optional but will produce exception if not supplied.
        if (mesh)
            this._dracoMeshes.set(ext, mesh);
    }
}
//# sourceMappingURL=GltfParser.js.map