UNPKG

@loaders.gl/gltf

Version:

Framework-independent loader for the glTF format

1,483 lines (1,449 loc) 239 kB
(function webpackUniversalModuleDefinition(root, factory) { if (typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if (typeof define === 'function' && define.amd) define([], factory); else if (typeof exports === 'object') exports['loaders'] = factory(); else root['loaders'] = factory();})(globalThis, function () { "use strict"; var __exports__ = (() => { var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __export = (target, all) => { for (var name12 in all) __defProp(target, name12, { get: all[name12], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // external-global-plugin:@loaders.gl/core var require_core = __commonJS({ "external-global-plugin:@loaders.gl/core"(exports, module) { module.exports = globalThis.loaders; } }); // bundle.ts var bundle_exports = {}; __export(bundle_exports, { EXT_FEATURE_METADATA: () => name3, EXT_MESH_FEATURES: () => name, EXT_STRUCTURAL_METADATA: () => name2, GLBLoader: () => GLBLoader, GLBWriter: () => GLBWriter, GLTFLoader: () => GLTFLoader, GLTFScenegraph: () => GLTFScenegraph, GLTFWriter: () => GLTFWriter, _getMemoryUsageGLTF: () => getMemoryUsageGLTF, createExtMeshFeatures: () => createExtMeshFeatures, createExtStructuralMetadata: () => createExtStructuralMetadata, postProcessGLTF: () => postProcessGLTF }); __reExport(bundle_exports, __toESM(require_core(), 1)); // src/lib/extensions/EXT_mesh_features.ts var EXT_mesh_features_exports = {}; __export(EXT_mesh_features_exports, { createExtMeshFeatures: () => createExtMeshFeatures, decode: () => decode, encode: () => encode, name: () => name }); // ../images/src/lib/utils/version.ts var VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "latest"; // ../loader-utils/src/loader-types.ts async function parseFromContext(data, loaders, options, context) { return context._parse(data, loaders, options, context); } // ../loader-utils/src/lib/env-utils/assert.ts function assert(condition, message) { if (!condition) { throw new Error(message || "loader assertion failed."); } } // ../loader-utils/src/lib/env-utils/globals.ts var globals = { self: typeof self !== "undefined" && self, window: typeof window !== "undefined" && window, global: typeof global !== "undefined" && global, document: typeof document !== "undefined" && document }; var self_ = globals.self || globals.window || globals.global || {}; var window_ = globals.window || globals.self || globals.global || {}; var global_ = globals.global || globals.self || globals.window || {}; var document_ = globals.document || {}; var isBrowser = ( // @ts-ignore process does not exist on browser Boolean(typeof process !== "object" || String(process) !== "[object process]" || process.browser) ); var matches = typeof process !== "undefined" && process.version && /v([0-9]*)/.exec(process.version); var nodeVersion = matches && parseFloat(matches[1]) || 0; // ../loader-utils/src/lib/module-utils/js-module-utils.ts function registerJSModules(modules) { globalThis.loaders ||= {}; globalThis.loaders.modules ||= {}; Object.assign(globalThis.loaders.modules, modules); } function getJSModuleOrNull(name12) { const module = globalThis.loaders?.modules?.[name12]; return module || null; } // ../worker-utils/src/lib/env-utils/version.ts var NPM_TAG = "latest"; function getVersion() { if (!globalThis._loadersgl_?.version) { globalThis._loadersgl_ = globalThis._loadersgl_ || {}; if (typeof __VERSION__ === "undefined") { console.warn( "loaders.gl: The __VERSION__ variable is not injected using babel plugin. Latest unstable workers would be fetched from the CDN." ); globalThis._loadersgl_.version = NPM_TAG; } else { globalThis._loadersgl_.version = __VERSION__; } } return globalThis._loadersgl_.version; } var VERSION2 = getVersion(); // ../worker-utils/src/lib/env-utils/assert.ts function assert2(condition, message) { if (!condition) { throw new Error(message || "loaders.gl assertion failed."); } } // ../worker-utils/src/lib/env-utils/globals.ts var globals2 = { self: typeof self !== "undefined" && self, window: typeof window !== "undefined" && window, global: typeof global !== "undefined" && global, document: typeof document !== "undefined" && document }; var self_2 = globals2.self || globals2.window || globals2.global || {}; var window_2 = globals2.window || globals2.self || globals2.global || {}; var global_2 = globals2.global || globals2.self || globals2.window || {}; var document_2 = globals2.document || {}; var isBrowser2 = ( // @ts-ignore process.browser typeof process !== "object" || String(process) !== "[object process]" || process.browser ); var isWorker = typeof importScripts === "function"; var isMobile = typeof window !== "undefined" && typeof window.orientation !== "undefined"; var matches2 = typeof process !== "undefined" && process.version && /v([0-9]*)/.exec(process.version); var nodeVersion2 = matches2 && parseFloat(matches2[1]) || 0; // ../worker-utils/src/lib/library-utils/library-utils.ts var loadLibraryPromises = {}; async function loadLibrary(libraryUrl, moduleName = null, options = {}, libraryName = null) { if (moduleName) { libraryUrl = getLibraryUrl(libraryUrl, moduleName, options, libraryName); } loadLibraryPromises[libraryUrl] = // eslint-disable-next-line @typescript-eslint/no-misused-promises loadLibraryPromises[libraryUrl] || loadLibraryFromFile(libraryUrl); return await loadLibraryPromises[libraryUrl]; } function getLibraryUrl(library, moduleName, options = {}, libraryName = null) { if (!options.useLocalLibraries && library.startsWith("http")) { return library; } libraryName = libraryName || library; const modules = options.modules || {}; if (modules[libraryName]) { return modules[libraryName]; } if (!isBrowser2) { return `modules/${moduleName}/dist/libs/${libraryName}`; } if (options.CDN) { assert2(options.CDN.startsWith("http")); return `${options.CDN}/${moduleName}@${VERSION2}/dist/libs/${libraryName}`; } if (isWorker) { return `../src/libs/${libraryName}`; } return `modules/${moduleName}/src/libs/${libraryName}`; } async function loadLibraryFromFile(libraryUrl) { if (libraryUrl.endsWith("wasm")) { return await loadAsArrayBuffer(libraryUrl); } if (!isBrowser2) { try { const { requireFromFile } = globalThis.loaders || {}; return await requireFromFile?.(libraryUrl); } catch (error) { console.error(error); return null; } } if (isWorker) { return importScripts(libraryUrl); } const scriptSource = await loadAsText(libraryUrl); return loadLibraryFromString(scriptSource, libraryUrl); } function loadLibraryFromString(scriptSource, id) { if (!isBrowser2) { const { requireFromString } = globalThis.loaders || {}; return requireFromString?.(scriptSource, id); } if (isWorker) { eval.call(globalThis, scriptSource); return null; } const script = document.createElement("script"); script.id = id; try { script.appendChild(document.createTextNode(scriptSource)); } catch (e) { script.text = scriptSource; } document.body.appendChild(script); return null; } async function loadAsArrayBuffer(url) { const { readFileAsArrayBuffer } = globalThis.loaders || {}; if (isBrowser2 || !readFileAsArrayBuffer || url.startsWith("http")) { const response = await fetch(url); return await response.arrayBuffer(); } return await readFileAsArrayBuffer(url); } async function loadAsText(url) { const { readFileAsText } = globalThis.loaders || {}; if (isBrowser2 || !readFileAsText || url.startsWith("http")) { const response = await fetch(url); return await response.text(); } return await readFileAsText(url); } // ../loader-utils/src/lib/binary-utils/get-first-characters.ts function getFirstCharacters(data, length = 5) { if (typeof data === "string") { return data.slice(0, length); } else if (ArrayBuffer.isView(data)) { return getMagicString(data.buffer, data.byteOffset, length); } else if (data instanceof ArrayBuffer) { const byteOffset = 0; return getMagicString(data, byteOffset, length); } return ""; } function getMagicString(arrayBuffer, byteOffset, length) { if (arrayBuffer.byteLength <= byteOffset + length) { return ""; } const dataView = new DataView(arrayBuffer); let magic = ""; for (let i = 0; i < length; i++) { magic += String.fromCharCode(dataView.getUint8(byteOffset + i)); } return magic; } // ../loader-utils/src/lib/parser-utils/parse-json.ts function parseJSON(string) { try { return JSON.parse(string); } catch (_) { throw new Error(`Failed to parse JSON from data starting with "${getFirstCharacters(string)}"`); } } // ../loader-utils/src/lib/binary-utils/array-buffer-utils.ts function sliceArrayBuffer(arrayBuffer, byteOffset, byteLength) { const subArray = byteLength !== void 0 ? new Uint8Array(arrayBuffer).subarray(byteOffset, byteOffset + byteLength) : new Uint8Array(arrayBuffer).subarray(byteOffset); const arrayCopy = new Uint8Array(subArray); return arrayCopy.buffer; } // ../loader-utils/src/lib/binary-utils/memory-copy-utils.ts function padToNBytes(byteLength, padding) { assert(byteLength >= 0); assert(padding > 0); return byteLength + (padding - 1) & ~(padding - 1); } function copyToArray(source, target, targetOffset) { let sourceArray; if (source instanceof ArrayBuffer) { sourceArray = new Uint8Array(source); } else { const srcByteOffset = source.byteOffset; const srcByteLength = source.byteLength; sourceArray = new Uint8Array(source.buffer || source.arrayBuffer, srcByteOffset, srcByteLength); } target.set(sourceArray, targetOffset); return targetOffset + padToNBytes(sourceArray.byteLength, 4); } // ../loader-utils/src/lib/binary-utils/dataview-copy-utils.ts function copyPaddedArrayBufferToDataView(dataView, byteOffset, sourceBuffer, padding) { const paddedLength = padToNBytes(sourceBuffer.byteLength, padding); const padLength = paddedLength - sourceBuffer.byteLength; if (dataView) { const targetArray = new Uint8Array( dataView.buffer, dataView.byteOffset + byteOffset, sourceBuffer.byteLength ); const sourceArray = new Uint8Array(sourceBuffer); targetArray.set(sourceArray); for (let i = 0; i < padLength; ++i) { dataView.setUint8(byteOffset + sourceBuffer.byteLength + i, 32); } } byteOffset += paddedLength; return byteOffset; } function copyPaddedStringToDataView(dataView, byteOffset, string, padding) { const textEncoder = new TextEncoder(); const stringBuffer = textEncoder.encode(string); byteOffset = copyPaddedArrayBufferToDataView(dataView, byteOffset, stringBuffer, padding); return byteOffset; } // ../images/src/lib/category-api/image-type.ts var parseImageNode = globalThis.loaders?.parseImageNode; var IMAGE_SUPPORTED = typeof Image !== "undefined"; var IMAGE_BITMAP_SUPPORTED = typeof ImageBitmap !== "undefined"; var NODE_IMAGE_SUPPORTED = Boolean(parseImageNode); var DATA_SUPPORTED = isBrowser ? true : NODE_IMAGE_SUPPORTED; function isImageTypeSupported(type) { switch (type) { case "auto": return IMAGE_BITMAP_SUPPORTED || IMAGE_SUPPORTED || DATA_SUPPORTED; case "imagebitmap": return IMAGE_BITMAP_SUPPORTED; case "image": return IMAGE_SUPPORTED; case "data": return DATA_SUPPORTED; default: throw new Error(`@loaders.gl/images: image ${type} not supported in this environment`); } } function getDefaultImageType() { if (IMAGE_BITMAP_SUPPORTED) { return "imagebitmap"; } if (IMAGE_SUPPORTED) { return "image"; } if (DATA_SUPPORTED) { return "data"; } throw new Error("Install '@loaders.gl/polyfills' to parse images under Node.js"); } // ../images/src/lib/category-api/parsed-image-api.ts function getImageType(image) { const format = getImageTypeOrNull(image); if (!format) { throw new Error("Not an image"); } return format; } function getImageData(image) { switch (getImageType(image)) { case "data": return image; case "image": case "imagebitmap": const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); if (!context) { throw new Error("getImageData"); } canvas.width = image.width; canvas.height = image.height; context.drawImage(image, 0, 0); return context.getImageData(0, 0, image.width, image.height); default: throw new Error("getImageData"); } } function getImageTypeOrNull(image) { if (typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) { return "imagebitmap"; } if (typeof Image !== "undefined" && image instanceof Image) { return "image"; } if (image && typeof image === "object" && image.data && image.width && image.height) { return "data"; } return null; } // ../images/src/lib/parsers/svg-utils.ts var SVG_DATA_URL_PATTERN = /^data:image\/svg\+xml/; var SVG_URL_PATTERN = /\.svg((\?|#).*)?$/; function isSVG(url) { return url && (SVG_DATA_URL_PATTERN.test(url) || SVG_URL_PATTERN.test(url)); } function getBlobOrSVGDataUrl(arrayBuffer, url) { if (isSVG(url)) { const textDecoder = new TextDecoder(); let xmlText = textDecoder.decode(arrayBuffer); try { if (typeof unescape === "function" && typeof encodeURIComponent === "function") { xmlText = unescape(encodeURIComponent(xmlText)); } } catch (error) { throw new Error(error.message); } const src = `data:image/svg+xml;base64,${btoa(xmlText)}`; return src; } return getBlob(arrayBuffer, url); } function getBlob(arrayBuffer, url) { if (isSVG(url)) { throw new Error("SVG cannot be parsed directly to imagebitmap"); } return new Blob([new Uint8Array(arrayBuffer)]); } // ../images/src/lib/parsers/parse-to-image.ts async function parseToImage(arrayBuffer, options, url) { const blobOrDataUrl = getBlobOrSVGDataUrl(arrayBuffer, url); const URL = self.URL || self.webkitURL; const objectUrl = typeof blobOrDataUrl !== "string" && URL.createObjectURL(blobOrDataUrl); try { return await loadToImage(objectUrl || blobOrDataUrl, options); } finally { if (objectUrl) { URL.revokeObjectURL(objectUrl); } } } async function loadToImage(url, options) { const image = new Image(); image.src = url; if (options.image && options.image.decode && image.decode) { await image.decode(); return image; } return await new Promise((resolve, reject) => { try { image.onload = () => resolve(image); image.onerror = (error) => { const message = error instanceof Error ? error.message : "error"; reject(new Error(message)); }; } catch (error) { reject(error); } }); } // ../images/src/lib/parsers/parse-to-image-bitmap.ts var EMPTY_OBJECT = {}; var imagebitmapOptionsSupported = true; async function parseToImageBitmap(arrayBuffer, options, url) { let blob; if (isSVG(url)) { const image = await parseToImage(arrayBuffer, options, url); blob = image; } else { blob = getBlob(arrayBuffer, url); } const imagebitmapOptions = options && options.imagebitmap; return await safeCreateImageBitmap(blob, imagebitmapOptions); } async function safeCreateImageBitmap(blob, imagebitmapOptions = null) { if (isEmptyObject(imagebitmapOptions) || !imagebitmapOptionsSupported) { imagebitmapOptions = null; } if (imagebitmapOptions) { try { return await createImageBitmap(blob, imagebitmapOptions); } catch (error) { console.warn(error); imagebitmapOptionsSupported = false; } } return await createImageBitmap(blob); } function isEmptyObject(object) { for (const key in object || EMPTY_OBJECT) { return false; } return true; } // ../images/src/lib/category-api/parse-isobmff-binary.ts function getISOBMFFMediaType(buffer) { if (!checkString(buffer, "ftyp", 4)) { return null; } if ((buffer[8] & 96) === 0) { return null; } return decodeMajorBrand(buffer); } function decodeMajorBrand(buffer) { const brandMajor = getUTF8String(buffer, 8, 12).replace("\0", " ").trim(); switch (brandMajor) { case "avif": case "avis": return { extension: "avif", mimeType: "image/avif" }; default: return null; } } function getUTF8String(array, start, end) { return String.fromCharCode(...array.slice(start, end)); } function stringToBytes(string) { return [...string].map((character) => character.charCodeAt(0)); } function checkString(buffer, header, offset = 0) { const headerBytes = stringToBytes(header); for (let i = 0; i < headerBytes.length; ++i) { if (headerBytes[i] !== buffer[i + offset]) { return false; } } return true; } // ../images/src/lib/category-api/binary-image-api.ts var BIG_ENDIAN = false; var LITTLE_ENDIAN = true; function getBinaryImageMetadata(binaryData) { const dataView = toDataView(binaryData); return getPngMetadata(dataView) || getJpegMetadata(dataView) || getGifMetadata(dataView) || getBmpMetadata(dataView) || getISOBMFFMetadata(dataView); } function getISOBMFFMetadata(binaryData) { const buffer = new Uint8Array(binaryData instanceof DataView ? binaryData.buffer : binaryData); const mediaType = getISOBMFFMediaType(buffer); if (!mediaType) { return null; } return { mimeType: mediaType.mimeType, // TODO - decode width and height width: 0, height: 0 }; } function getPngMetadata(binaryData) { const dataView = toDataView(binaryData); const isPng = dataView.byteLength >= 24 && dataView.getUint32(0, BIG_ENDIAN) === 2303741511; if (!isPng) { return null; } return { mimeType: "image/png", width: dataView.getUint32(16, BIG_ENDIAN), height: dataView.getUint32(20, BIG_ENDIAN) }; } function getGifMetadata(binaryData) { const dataView = toDataView(binaryData); const isGif = dataView.byteLength >= 10 && dataView.getUint32(0, BIG_ENDIAN) === 1195984440; if (!isGif) { return null; } return { mimeType: "image/gif", width: dataView.getUint16(6, LITTLE_ENDIAN), height: dataView.getUint16(8, LITTLE_ENDIAN) }; } function getBmpMetadata(binaryData) { const dataView = toDataView(binaryData); const isBmp = dataView.byteLength >= 14 && dataView.getUint16(0, BIG_ENDIAN) === 16973 && dataView.getUint32(2, LITTLE_ENDIAN) === dataView.byteLength; if (!isBmp) { return null; } return { mimeType: "image/bmp", width: dataView.getUint32(18, LITTLE_ENDIAN), height: dataView.getUint32(22, LITTLE_ENDIAN) }; } function getJpegMetadata(binaryData) { const dataView = toDataView(binaryData); const isJpeg = dataView.byteLength >= 3 && dataView.getUint16(0, BIG_ENDIAN) === 65496 && dataView.getUint8(2) === 255; if (!isJpeg) { return null; } const { tableMarkers, sofMarkers } = getJpegMarkers(); let i = 2; while (i + 9 < dataView.byteLength) { const marker = dataView.getUint16(i, BIG_ENDIAN); if (sofMarkers.has(marker)) { return { mimeType: "image/jpeg", height: dataView.getUint16(i + 5, BIG_ENDIAN), // Number of lines width: dataView.getUint16(i + 7, BIG_ENDIAN) // Number of pixels per line }; } if (!tableMarkers.has(marker)) { return null; } i += 2; i += dataView.getUint16(i, BIG_ENDIAN); } return null; } function getJpegMarkers() { const tableMarkers = /* @__PURE__ */ new Set([65499, 65476, 65484, 65501, 65534]); for (let i = 65504; i < 65520; ++i) { tableMarkers.add(i); } const sofMarkers = /* @__PURE__ */ new Set([ 65472, 65473, 65474, 65475, 65477, 65478, 65479, 65481, 65482, 65483, 65485, 65486, 65487, 65502 ]); return { tableMarkers, sofMarkers }; } function toDataView(data) { if (data instanceof DataView) { return data; } if (ArrayBuffer.isView(data)) { return new DataView(data.buffer); } if (data instanceof ArrayBuffer) { return new DataView(data); } throw new Error("toDataView"); } // ../images/src/lib/parsers/parse-to-node-image.ts async function parseToNodeImage(arrayBuffer, options) { const { mimeType } = getBinaryImageMetadata(arrayBuffer) || {}; const parseImageNode2 = globalThis.loaders?.parseImageNode; assert(parseImageNode2); return await parseImageNode2(arrayBuffer, mimeType); } // ../images/src/lib/parsers/parse-image.ts async function parseImage(arrayBuffer, options, context) { options = options || {}; const imageOptions = options.image || {}; const imageType = imageOptions.type || "auto"; const { url } = context || {}; const loadType = getLoadableImageType(imageType); let image; switch (loadType) { case "imagebitmap": image = await parseToImageBitmap(arrayBuffer, options, url); break; case "image": image = await parseToImage(arrayBuffer, options, url); break; case "data": image = await parseToNodeImage(arrayBuffer, options); break; default: assert(false); } if (imageType === "data") { image = getImageData(image); } return image; } function getLoadableImageType(type) { switch (type) { case "auto": case "data": return getDefaultImageType(); default: isImageTypeSupported(type); return type; } } // ../images/src/image-loader.ts var EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp", "bmp", "ico", "svg", "avif"]; var MIME_TYPES = [ "image/png", "image/jpeg", "image/gif", "image/webp", "image/avif", "image/bmp", "image/vnd.microsoft.icon", "image/svg+xml" ]; var DEFAULT_IMAGE_LOADER_OPTIONS = { image: { type: "auto", decode: true // if format is HTML } // imagebitmap: {} - passes (platform dependent) parameters to ImageBitmap constructor }; var ImageLoader = { dataType: null, batchType: null, id: "image", module: "images", name: "Images", version: VERSION, mimeTypes: MIME_TYPES, extensions: EXTENSIONS, parse: parseImage, // TODO: byteOffset, byteLength; tests: [(arrayBuffer) => Boolean(getBinaryImageMetadata(new DataView(arrayBuffer)))], options: DEFAULT_IMAGE_LOADER_OPTIONS }; // ../images/src/lib/category-api/image-format.ts var mimeTypeSupportedSync = {}; function isImageFormatSupported(mimeType) { if (mimeTypeSupportedSync[mimeType] === void 0) { const supported = isBrowser ? checkBrowserImageFormatSupport(mimeType) : checkNodeImageFormatSupport(mimeType); mimeTypeSupportedSync[mimeType] = supported; } return mimeTypeSupportedSync[mimeType]; } function checkNodeImageFormatSupport(mimeType) { const NODE_FORMAT_SUPPORT = ["image/png", "image/jpeg", "image/gif"]; const imageFormatsNode = globalThis.loaders?.imageFormatsNode || NODE_FORMAT_SUPPORT; const parseImageNode2 = globalThis.loaders?.parseImageNode; return Boolean(parseImageNode2) && imageFormatsNode.includes(mimeType); } function checkBrowserImageFormatSupport(mimeType) { switch (mimeType) { case "image/avif": case "image/webp": return testBrowserImageFormatSupport(mimeType); default: return true; } } function testBrowserImageFormatSupport(mimeType) { try { const element = document.createElement("canvas"); const dataURL = element.toDataURL(mimeType); return dataURL.indexOf(`data:${mimeType}`) === 0; } catch { return false; } } // src/lib/utils/assert.ts function assert3(condition, message) { if (!condition) { throw new Error(message || "assert failed: gltf"); } } // src/lib/gltf-utils/gltf-constants.ts var COMPONENTS = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4, MAT2: 4, MAT3: 9, MAT4: 16 }; var BYTES = { 5120: 1, // BYTE 5121: 1, // UNSIGNED_BYTE 5122: 2, // SHORT 5123: 2, // UNSIGNED_SHORT 5125: 4, // UNSIGNED_INT 5126: 4 // FLOAT }; // src/lib/gltf-utils/gltf-utils.ts var MIPMAP_FACTOR = 1.33; var TYPES = ["SCALAR", "VEC2", "VEC3", "VEC4"]; var ARRAY_CONSTRUCTOR_TO_WEBGL_CONSTANT = [ [Int8Array, 5120], [Uint8Array, 5121], [Int16Array, 5122], [Uint16Array, 5123], [Uint32Array, 5125], [Float32Array, 5126], [Float64Array, 5130] ]; var ARRAY_TO_COMPONENT_TYPE = new Map( ARRAY_CONSTRUCTOR_TO_WEBGL_CONSTANT ); var ATTRIBUTE_TYPE_TO_COMPONENTS = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4, MAT2: 4, MAT3: 9, MAT4: 16 }; var ATTRIBUTE_COMPONENT_TYPE_TO_BYTE_SIZE = { 5120: 1, 5121: 1, 5122: 2, 5123: 2, 5125: 4, 5126: 4 }; var ATTRIBUTE_COMPONENT_TYPE_TO_ARRAY = { 5120: Int8Array, 5121: Uint8Array, 5122: Int16Array, 5123: Uint16Array, 5125: Uint32Array, 5126: Float32Array }; function getAccessorTypeFromSize(size) { const type = TYPES[size - 1]; return type || TYPES[0]; } function getComponentTypeFromArray(typedArray) { const componentType = ARRAY_TO_COMPONENT_TYPE.get(typedArray.constructor); if (!componentType) { throw new Error("Illegal typed array"); } return componentType; } function getAccessorArrayTypeAndLength(accessor, bufferView) { const ArrayType = ATTRIBUTE_COMPONENT_TYPE_TO_ARRAY[accessor.componentType]; const components = ATTRIBUTE_TYPE_TO_COMPONENTS[accessor.type]; const bytesPerComponent = ATTRIBUTE_COMPONENT_TYPE_TO_BYTE_SIZE[accessor.componentType]; const length = accessor.count * components; const byteLength = accessor.count * components * bytesPerComponent; assert3(byteLength >= 0 && byteLength <= bufferView.byteLength); const componentByteSize = BYTES[accessor.componentType]; const numberOfComponentsInElement = COMPONENTS[accessor.type]; return { ArrayType, length, byteLength, componentByteSize, numberOfComponentsInElement }; } function getMemoryUsageGLTF(gltf) { let { images, bufferViews } = gltf; images = images || []; bufferViews = bufferViews || []; const imageBufferViews = images.map((i) => i.bufferView); bufferViews = bufferViews.filter((view) => !imageBufferViews.includes(view)); const bufferMemory = bufferViews.reduce((acc, view) => acc + view.byteLength, 0); const pixelCount = images.reduce((acc, image) => { const { width, height } = image.image; return acc + width * height; }, 0); return bufferMemory + Math.ceil(4 * pixelCount * MIPMAP_FACTOR); } // src/lib/gltf-utils/get-typed-array.ts function getTypedArrayForBufferView(json, buffers, bufferViewIndex) { const bufferView = json.bufferViews[bufferViewIndex]; assert3(bufferView); const bufferIndex = bufferView.buffer; const binChunk = buffers[bufferIndex]; assert3(binChunk); const byteOffset = (bufferView.byteOffset || 0) + binChunk.byteOffset; return new Uint8Array(binChunk.arrayBuffer, byteOffset, bufferView.byteLength); } function getTypedArrayForAccessor(json, buffers, accessor) { const gltfAccessor = typeof accessor === "number" ? json.accessors?.[accessor] : accessor; if (!gltfAccessor) { throw new Error(`No gltf accessor ${JSON.stringify(accessor)}`); } const bufferView = json.bufferViews?.[gltfAccessor.bufferView || 0]; if (!bufferView) { throw new Error(`No gltf buffer view for accessor ${bufferView}`); } const { arrayBuffer, byteOffset: bufferByteOffset } = buffers[bufferView.buffer]; const byteOffset = (bufferByteOffset || 0) + (gltfAccessor.byteOffset || 0) + (bufferView.byteOffset || 0); const { ArrayType, length, componentByteSize, numberOfComponentsInElement } = getAccessorArrayTypeAndLength(gltfAccessor, bufferView); const elementByteSize = componentByteSize * numberOfComponentsInElement; const elementAddressScale = bufferView.byteStride || elementByteSize; if (typeof bufferView.byteStride === "undefined" || bufferView.byteStride === elementByteSize) { const result2 = new ArrayType(arrayBuffer, byteOffset, length); return result2; } const result = new ArrayType(length); for (let i = 0; i < gltfAccessor.count; i++) { const values = new ArrayType( arrayBuffer, byteOffset + i * elementAddressScale, numberOfComponentsInElement ); result.set(values, i * numberOfComponentsInElement); } return result; } // src/lib/api/gltf-scenegraph.ts function makeDefaultGLTFJson() { return { asset: { version: "2.0", generator: "loaders.gl" }, buffers: [], extensions: {}, extensionsRequired: [], extensionsUsed: [] }; } var GLTFScenegraph = class { // internal gltf; sourceBuffers; byteLength; // TODO - why is this not GLTFWithBuffers - what happens to images? constructor(gltf) { this.gltf = { json: gltf?.json || makeDefaultGLTFJson(), buffers: gltf?.buffers || [], images: gltf?.images || [] }; this.sourceBuffers = []; this.byteLength = 0; if (this.gltf.buffers && this.gltf.buffers[0]) { this.byteLength = this.gltf.buffers[0].byteLength; this.sourceBuffers = [this.gltf.buffers[0]]; } } // Accessors get json() { return this.gltf.json; } getApplicationData(key) { const data = this.json[key]; return data; } getExtraData(key) { const extras = this.json.extras || {}; return extras[key]; } hasExtension(extensionName) { const isUsedExtension = this.getUsedExtensions().find((name12) => name12 === extensionName); const isRequiredExtension = this.getRequiredExtensions().find((name12) => name12 === extensionName); return typeof isUsedExtension === "string" || typeof isRequiredExtension === "string"; } getExtension(extensionName) { const isExtension = this.getUsedExtensions().find((name12) => name12 === extensionName); const extensions = this.json.extensions || {}; return isExtension ? extensions[extensionName] : null; } getRequiredExtension(extensionName) { const isRequired = this.getRequiredExtensions().find((name12) => name12 === extensionName); return isRequired ? this.getExtension(extensionName) : null; } getRequiredExtensions() { return this.json.extensionsRequired || []; } getUsedExtensions() { return this.json.extensionsUsed || []; } getRemovedExtensions() { return this.json.extensionsRemoved || []; } getObjectExtension(object, extensionName) { const extensions = object.extensions || {}; return extensions[extensionName]; } getScene(index) { return this.getObject("scenes", index); } getNode(index) { return this.getObject("nodes", index); } getSkin(index) { return this.getObject("skins", index); } getMesh(index) { return this.getObject("meshes", index); } getMaterial(index) { return this.getObject("materials", index); } getAccessor(index) { return this.getObject("accessors", index); } // getCamera(index: number): object | null { // return null; // TODO: fix thi: object as null; // } getTexture(index) { return this.getObject("textures", index); } getSampler(index) { return this.getObject("samplers", index); } getImage(index) { return this.getObject("images", index); } getBufferView(index) { return this.getObject("bufferViews", index); } getBuffer(index) { return this.getObject("buffers", index); } getObject(array, index) { if (typeof index === "object") { return index; } const object = this.json[array] && this.json[array][index]; if (!object) { throw new Error(`glTF file error: Could not find ${array}[${index}]`); } return object; } /** * Accepts buffer view index or buffer view object * @returns a `Uint8Array` */ getTypedArrayForBufferView(bufferView) { bufferView = this.getBufferView(bufferView); const bufferIndex = bufferView.buffer; const binChunk = this.gltf.buffers[bufferIndex]; assert3(binChunk); const byteOffset = (bufferView.byteOffset || 0) + binChunk.byteOffset; return new Uint8Array(binChunk.arrayBuffer, byteOffset, bufferView.byteLength); } /** Accepts accessor index or accessor object * @returns a typed array with type that matches the types */ getTypedArrayForAccessor(accessor) { const gltfAccessor = this.getAccessor(accessor); return getTypedArrayForAccessor(this.gltf.json, this.gltf.buffers, gltfAccessor); } /** accepts accessor index or accessor object * returns a `Uint8Array` */ getTypedArrayForImageData(image) { image = this.getAccessor(image); const bufferView = this.getBufferView(image.bufferView); const buffer = this.getBuffer(bufferView.buffer); const arrayBuffer = buffer.data; const byteOffset = bufferView.byteOffset || 0; return new Uint8Array(arrayBuffer, byteOffset, bufferView.byteLength); } // MODIFERS /** * Add an extra application-defined key to the top-level data structure */ addApplicationData(key, data) { this.json[key] = data; return this; } /** * `extras` - Standard GLTF field for storing application specific data */ addExtraData(key, data) { this.json.extras = this.json.extras || {}; this.json.extras[key] = data; return this; } addObjectExtension(object, extensionName, data) { object.extensions = object.extensions || {}; object.extensions[extensionName] = data; this.registerUsedExtension(extensionName); return this; } setObjectExtension(object, extensionName, data) { const extensions = object.extensions || {}; extensions[extensionName] = data; } removeObjectExtension(object, extensionName) { const extensions = object?.extensions || {}; if (extensions[extensionName]) { this.json.extensionsRemoved = this.json.extensionsRemoved || []; const extensionsRemoved = this.json.extensionsRemoved; if (!extensionsRemoved.includes(extensionName)) { extensionsRemoved.push(extensionName); } } delete extensions[extensionName]; } /** * Add to standard GLTF top level extension object, mark as used */ addExtension(extensionName, extensionData = {}) { assert3(extensionData); this.json.extensions = this.json.extensions || {}; this.json.extensions[extensionName] = extensionData; this.registerUsedExtension(extensionName); return extensionData; } /** * Standard GLTF top level extension object, mark as used and required */ addRequiredExtension(extensionName, extensionData = {}) { assert3(extensionData); this.addExtension(extensionName, extensionData); this.registerRequiredExtension(extensionName); return extensionData; } /** * Add extensionName to list of used extensions */ registerUsedExtension(extensionName) { this.json.extensionsUsed = this.json.extensionsUsed || []; if (!this.json.extensionsUsed.find((ext) => ext === extensionName)) { this.json.extensionsUsed.push(extensionName); } } /** * Add extensionName to list of required extensions */ registerRequiredExtension(extensionName) { this.registerUsedExtension(extensionName); this.json.extensionsRequired = this.json.extensionsRequired || []; if (!this.json.extensionsRequired.find((ext) => ext === extensionName)) { this.json.extensionsRequired.push(extensionName); } } /** * Removes an extension from the top-level list */ removeExtension(extensionName) { if (this.json.extensions?.[extensionName]) { this.json.extensionsRemoved = this.json.extensionsRemoved || []; const extensionsRemoved = this.json.extensionsRemoved; if (!extensionsRemoved.includes(extensionName)) { extensionsRemoved.push(extensionName); } } if (this.json.extensions) { delete this.json.extensions[extensionName]; } if (this.json.extensionsRequired) { this._removeStringFromArray(this.json.extensionsRequired, extensionName); } if (this.json.extensionsUsed) { this._removeStringFromArray(this.json.extensionsUsed, extensionName); } } /** * Set default scene which is to be displayed at load time */ setDefaultScene(sceneIndex) { this.json.scene = sceneIndex; } /** * @todo: add more properties for scene initialization: * name`, `extensions`, `extras` * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-scene */ addScene(scene) { const { nodeIndices } = scene; this.json.scenes = this.json.scenes || []; this.json.scenes.push({ nodes: nodeIndices }); return this.json.scenes.length - 1; } /** * @todo: add more properties for node initialization: * `name`, `extensions`, `extras`, `camera`, `children`, `skin`, `rotation`, `scale`, `translation`, `weights` * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#node */ addNode(node) { const { meshIndex, matrix } = node; this.json.nodes = this.json.nodes || []; const nodeData = { mesh: meshIndex }; if (matrix) { nodeData.matrix = matrix; } this.json.nodes.push(nodeData); return this.json.nodes.length - 1; } /** Adds a mesh to the json part */ addMesh(mesh) { const { attributes, indices, material, mode = 4 } = mesh; const accessors = this._addAttributes(attributes); const glTFMesh = { primitives: [ { attributes: accessors, mode } ] }; if (indices) { const indicesAccessor = this._addIndices(indices); glTFMesh.primitives[0].indices = indicesAccessor; } if (Number.isFinite(material)) { glTFMesh.primitives[0].material = material; } this.json.meshes = this.json.meshes || []; this.json.meshes.push(glTFMesh); return this.json.meshes.length - 1; } addPointCloud(attributes) { const accessorIndices = this._addAttributes(attributes); const glTFMesh = { primitives: [ { attributes: accessorIndices, mode: 0 // GL.POINTS } ] }; this.json.meshes = this.json.meshes || []; this.json.meshes.push(glTFMesh); return this.json.meshes.length - 1; } /** * Adds a binary image. Builds glTF "JSON metadata" and saves buffer reference * Buffer will be copied into BIN chunk during "pack" * Currently encodes as glTF image * @param imageData * @param mimeType */ addImage(imageData, mimeTypeOpt) { const metadata = getBinaryImageMetadata(imageData); const mimeType = mimeTypeOpt || metadata?.mimeType; const bufferViewIndex = this.addBufferView(imageData); const glTFImage = { bufferView: bufferViewIndex, mimeType }; this.json.images = this.json.images || []; this.json.images.push(glTFImage); return this.json.images.length - 1; } /** * Add one untyped source buffer, create a matching glTF `bufferView`, and return its index * @param buffer */ addBufferView(buffer, bufferIndex = 0, byteOffset = this.byteLength) { const byteLength = buffer.byteLength; assert3(Number.isFinite(byteLength)); this.sourceBuffers = this.sourceBuffers || []; this.sourceBuffers.push(buffer); const glTFBufferView = { buffer: bufferIndex, // Write offset from the start of the binary body byteOffset, byteLength }; this.byteLength += padToNBytes(byteLength, 4); this.json.bufferViews = this.json.bufferViews || []; this.json.bufferViews.push(glTFBufferView); return this.json.bufferViews.length - 1; } /** * Adds an accessor to a bufferView * @param bufferViewIndex * @param accessor */ addAccessor(bufferViewIndex, accessor) { const glTFAccessor = { bufferView: bufferViewIndex, // @ts-ignore type: getAccessorTypeFromSize(accessor.size), // @ts-ignore componentType: accessor.componentType, // @ts-ignore count: accessor.count, // @ts-ignore max: accessor.max, // @ts-ignore min: accessor.min }; this.json.accessors = this.json.accessors || []; this.json.accessors.push(glTFAccessor); return this.json.accessors.length - 1; } /** * Add a binary buffer. Builds glTF "JSON metadata" and saves buffer reference * Buffer will be copied into BIN chunk during "pack" * Currently encodes buffers as glTF accessors, but this could be optimized * @param sourceBuffer * @param accessor */ addBinaryBuffer(sourceBuffer, accessor = { size: 3 }) { const bufferViewIndex = this.addBufferView(sourceBuffer); let minMax = { min: accessor.min, max: accessor.max }; if (!minMax.min || !minMax.max) { minMax = this._getAccessorMinMax(sourceBuffer, accessor.size); } const accessorDefaults = { // @ts-ignore size: accessor.size, componentType: getComponentTypeFromArray(sourceBuffer), // @ts-ignore count: Math.round(sourceBuffer.length / accessor.size), min: minMax.min, max: minMax.max }; return this.addAccessor(bufferViewIndex, Object.assign(accessorDefaults, accessor)); } /** * Adds a texture to the json part * @todo: add more properties for texture initialization * `sampler`, `name`, `extensions`, `extras` * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#texture */ addTexture(texture) { const { imageIndex } = texture; const glTFTexture = { source: imageIndex }; this.json.textures = this.json.textures || []; this.json.textures.push(glTFTexture); return this.json.textures.length - 1; } /** Adds a material to the json part */ addMaterial(pbrMaterialInfo) { this.json.materials = this.json.materials || []; this.json.materials.push(pbrMaterialInfo); return this.json.materials.length - 1; } /** Pack the binary chunk */ createBinaryChunk() { const totalByteLength = this.byteLength; const arrayBuffer = new ArrayBuffer(totalByteLength); const targetArray = new Uint8Array(arrayBuffer); let dstByteOffset = 0; for (const sourceBuffer of this.sourceBuffers || []) { dstByteOffset = copyToArray(sourceBuffer, targetArray, dstByteOffset); } if (this.json?.buffers?.[0]) { this.json.buffers[0].byteLength = totalByteLength; } else { this.json.buffers = [{ byteLength: totalByteLength }]; } this.gltf.binary = arrayBuffer; this.sourceBuffers = [arrayBuffer]; this.gltf.buffers = [{ arrayBuffer, byteOffset: 0, byteLength: arrayBuffer.byteLength }]; } // PRIVATE _removeStringFromArray(array, string) { let found = true; while (found) { const index = array.indexOf(string); if (index > -1) { array.splice(index, 1); } else { found = false; } } } /** * Add attributes to buffers and create `attributes` object which is part of `mesh` */ _addAttributes(attributes = {}) { const result = {}; for (const attributeKey in attributes) { const attributeData = attributes[attributeKey]; const attrName = this._getGltfAttributeName(attributeKey); const accessor = this.addBinaryBuffer(attributeData.value, attributeData); result[attrName] = accessor; } return result; } /** * Add indices to buffers */ _addIndices(indices) { return this.addBinaryBuffer(indices, { size: 1 }); } /** * Deduce gltf specific attribue name from input attribute name */ _getGltfAttributeName(attributeName) { switch (attributeName.toLowerCase()) { case "position": case "positions": case "vertices": return "POSITION"; case "normal": case "normals": return "NORMAL"; case "color": case "colors": return "COLOR_0"; case "texcoord": case "texcoords": return "TEXCOORD_0"; default: return attributeName; } } /** * Calculate `min` and `max` arrays of accessor according to spec: * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-accessor */ _getAccessorMinMax(buffer, size) { const result = { min: null, max: null }; if (buffer.length < size) { return result; } result.min = []; result.max = []; const initValues = buffer.subarray(0, size); for (const value of initValues) { result.min.push(value); result.max.push(value); } for (let index = size; index < buffer.length; index += size) { for (let componentIndex = 0; componentIndex < size; componentIndex++) { result.min[0 + componentIndex] = Math.min( // @ts-ignore result.min[0 + componentIndex], buffer[index + componentIndex] ); result.max[0 + componentIndex] = Math.max( // @ts-ignore result.max[0 + componentIndex], buffer[index + componentIndex] ); } } return result; } }; // src/lib/extensions/utils/3d-tiles-utils.ts function emod(n) { return (n % 1 + 1) % 1; } var ATTRIBUTE_TYPE_TO_COMPONENTS2 = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4, MAT2: 4, MAT3: 9, MAT4: 16, BOOLEAN: 1, STRING: 1, ENUM: 1 }; var ATTRIBUTE_COMPONENT_TYPE_TO_ARRAY2 = { INT8: Int8Array, UINT8: Uint8Array, INT16: Int16Array, UINT16: Uint16Array, INT32: Int32Array, UINT32: Uint32Array, INT64: BigInt64Array, UINT64: BigUint64Array, FLOAT32: Float32Array, FLOAT64: Float64Array }; var ATTRIBUTE_COMPONENT_TYPE_TO_BYTE_SIZE2 = { INT8: 1, UINT8: 1, INT16: 2, UINT16: 2, INT32: 4, UINT32: 4, INT64: 8, UIN