| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- import { Loader } from 'three'; // or https://cdn.jsdelivr.net/npm/three/build/three.module.js';
- // WASM module of TinyUSDZ.
- import initTinyUSDZNative from './tinyusdz.js';
- class FetchAssetResolver {
- constructor() {
- this.assetCache = new Map();
- }
- async resolveAsync(uri) {
- try {
- const response = await fetch(uri);
- if (!response.ok) {
- throw new Error(`Failed to fetch asset: ${uri}`);
- }
- const data = await response.arrayBuffer();
- //console.log(`Fetched asset ${uri} successfully, size: ${data.byteLength} bytes`);
- this.assetCache.set(uri, data);
- return Promise.resolve([uri, data]);
- } catch (error) {
- console.error(`Error resolving asset ${uri}:`, error);
- throw error;
- }
- }
- getAsset(uri) {
- if (this.assetCache.has(uri)) {
- return this.assetCache.get(uri);
- } else {
- console.warn(`Asset not found in cache: ${uri}`);
- return null;
- }
- }
- hasAsset(uri) {
- return this.assetCache.has(uri);
- }
- setAsset(uri, data) {
- this.assetCache.set(uri, data);
- }
- clearCache() {
- this.assetCache.clear();
- }
- }
- // TODO
- //
- // Polish API
- //
- class TinyUSDZLoader extends Loader {
- constructor(manager) {
- super(manager);
- this.native_ = null;
- this.assetResolver_ = null;
- // texture loader callback
- // null = Use TinyUSDZ's builtin image loader(C++ native module)
- //this.texLoader = null;
- this.imageCache = {};
- this.textureCache = {};
- // Default: do NOT use zstd compressed WASM.
- this.useZstdCompressedWasm_ = false;
- this.compressedWasmPath_ = 'tinyusdz.wasm.zst';
- }
- // Decompress zstd compressed WASM
- async decompressZstdWasm(compressedPath) {
- try {
- const fzstd = await import('fzstd');
- const wasmURL = new URL(compressedPath, import.meta.url).href;
- //console.log(`Loading compressed WASM from: ${wasmURL}`);
- const response = await fetch(wasmURL);
- //console.log(response);
- if (!response.ok) {
- throw new Error(`Failed to fetch compressed WASM: ${response.statusText}`);
- }
- const compressedData = await response.arrayBuffer();
- //console.log(`Compressed WASM size: ${compressedData.byteLength} bytes`);
- if (compressedData.byteLength < 1024*64) {
- throw new Error('Compressed WASM size is unusually small, may not be valid zstd compressed data.');
- }
- // Check zstd magic number (0x28B52FFD in little-endian)
- const magicBytes = new Uint8Array(compressedData, 0, 4);
- const expectedMagic = [0x28, 0xB5, 0x2F, 0xFD]; // Little-endian representation
- //console.log(magicBytes);
- //console.log(expectedMagic);
-
- if (compressedData.byteLength < 4 ||
- magicBytes[0] !== expectedMagic[0] ||
- magicBytes[1] !== expectedMagic[1] ||
- magicBytes[2] !== expectedMagic[2] ||
- magicBytes[3] !== expectedMagic[3]) {
- throw new Error('Invalid zstd file: magic number mismatch');
- }
- // Decompress using zstd
- const decompressedData = fzstd.decompress(new Uint8Array(compressedData));
- //console.log(`Decompressed WASM size: ${decompressedData.byteLength} bytes`);
- return decompressedData;
- } catch (error) {
- console.error('Error decompressing zstd WASM:', error);
- throw error;
- }
- }
- // Initialize the native WASM module
- // This is async but the load() method handles it internally with promises
- async init( options = {}) {
- if (Object.prototype.hasOwnProperty.call(options, 'useZstdCompressedWasm')) {
- this.useZstdCompressedWasm_ = options.useZstdCompressedWasm;
- }
- if (!this.native_) {
- //console.log('Initializing native module...');
- let wasmBinary = null;
-
- if (this.useZstdCompressedWasm_) {
- // Load and decompress zstd compressed WASM
- wasmBinary = await this.decompressZstdWasm(this.compressedWasmPath_);
- }
- // Initialize with custom WASM binary if decompressed
- const initOptions = wasmBinary ? { wasmBinary } : {};
- this.native_ = await initTinyUSDZNative(initOptions);
- if (!this.native_) {
- throw new Error('TinyUSDZLoader: Failed to initialize native module.');
- }
- //console.log('Native module initialized');
- }
- return this;
- }
- // TODO: remove
- // Set AssetResolver callback.
- // This is used to resolve asset paths(e.g. textures, usd files) in the USD.
- // For web app, usually we'll convert asset path to URI
- //setAssetResolver(callback) {
- // this.assetResolver_ = callback;
- //}
- //
- // Load a USDZ/USDA/USDC file from a URL as USD Stage(Freezed scene graph)
- // NOTE: for loadAsync(), Use base Loader class's loadAsync() method
- //
- load(url, onLoad, onProgress, onError) {
- //console.log('url', url);
- const scope = this;
- // Create a promise chain to handle initialization and loading
- const initPromise = this.native_ ? Promise.resolve() : this.init();
- initPromise
- .then(() => {
- return fetch(url);
- })
- .then((response) => {
- return response.arrayBuffer();
- })
- .then((usd_data) => {
- const usd_binary = new Uint8Array(usd_data);
- //console.log('Loaded USD binary data:', usd_binary.length, 'bytes');
- scope.parse(usd_binary, url, function (usd) {
- onLoad(usd);
- }, onError);
- })
- .catch((error) => {
- console.error('TinyUSDZLoader: Error initializing native module:', error);
- if (onError) {
- onError(error);
- }
- });
- }
- //
- // Parse a USDZ/USDA/USDC binary data
- //
- parse(binary /* ArrayBuffer */, filePath /* optional */, onLoad, onError) {
- const _onError = function (e) {
- if (onError) {
- onError(e);
- } else {
- console.error(e);
- }
- //scope.manager.itemError( url );
- //scope.manager.itemEnd( url );
- };
- if (!this.native_) {
- console.error('TinyUSDZLoader: Native module is not initialized.');
- _onError(new Error('TinyUSDZLoader: Native module is not initialized.'));
- }
- const usd = new this.native_.TinyUSDZLoaderNative();
- const ok = usd.loadFromBinary(binary, filePath);
- if (!ok) {
- _onError(new Error('TinyUSDZLoader: Failed to load USD from binary data.', {cause: usd.error()}));
- } else {
- onLoad(usd);
- }
- }
- //
- // Load a USDZ/USDA/USDC file from a URL as USD Layer(for composition)
- //
- loadAsLayer(url, onLoad, onProgress, onError) {
- //console.log('url', url);
- const scope = this;
- const _onError = function (e) {
- if (onError) {
- onError(e);
- } else {
- console.error(e);
- }
- //scope.manager.itemError( url );
- //scope.manager.itemEnd( url );
- };
- // Create a promise chain to handle initialization and loading
- const initPromise = this.native_ ? Promise.resolve() : this.init();
- initPromise
- .then(() => {
- //usd_ = new this.native_.TinyUSDZLoaderNative();
- return fetch(url);
- })
- .then((response) => {
- //console.log('fetch USDZ file done:', url);
- return response.arrayBuffer();
- })
- .then((usd_data) => {
- const usd_binary = new Uint8Array(usd_data);
- //console.log('Loaded USD binary data:', usd_binary.length, 'bytes');
- //return this.parse(usd_binary);
- const usd = new this.native_.TinyUSDZLoaderNative();
- const ok = usd.loadAsLayerFromBinary(usd_binary, url);
- if (!ok) {
- _onError(new Error('TinyUSDZLoader: Failed to load USD as Layer from binary data. url: ' + url, {cause: usd.error()}));
- } else {
- onLoad(usd);
- }
- })
- .catch((error) => {
- console.error('TinyUSDZLoader: Error initializing native module:', error);
- if (onError) {
- onError(error);
- }
- });
- }
- async loadAsLayerAsync(url, onProgress) {
- const scope = this;
- return new Promise( function ( resolve, reject ) {
- scope.loadAsLayer( url, resolve, onProgress, reject );
- } );
- }
- ///**
- // * Set texture callback
- // */
- //setTextureLoader(texLoader) {
- // this.texLoader = texLoader;
- //}
- }
- export { TinyUSDZLoader, FetchAssetResolver };
|