@itwin/core-frontend
Version:
iTwin.js frontend components
368 lines • 16.2 kB
JavaScript
"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