UNPKG

@itwin/core-frontend

Version:
368 lines • 16.2 kB
"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 = parseGltf; 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(); } class GltfParser { _version; _upAxis; _baseUrl; _logger; _isCanceled; _buffers; _images; _nodes; _meshes; _accessors; _sceneNodes; _bufferViews; _imageFromImageSource; _dracoMeshes = new Map(); constructor(options) { 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