|
@@ -1,320 +1,117 @@
|
|
|
-import { Loader } from 'three'; // or https://cdn.jsdelivr.net/npm/three/build/three.module.js';
|
|
|
|
|
|
|
+import { Loader } from "three"; // or https://cdn.jsdelivr.net/npm/three/build/three.module.js';
|
|
|
|
|
|
|
|
// WASM module of TinyUSDZ.
|
|
// WASM module of TinyUSDZ.
|
|
|
-import initTinyUSDZNative from './tinyusdz.js';
|
|
|
|
|
-
|
|
|
|
|
|
|
+import initTinyUSDZNative from "./tinyusdz.js";
|
|
|
|
|
|
|
|
class FetchAssetResolver {
|
|
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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ 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;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- hasAsset(uri) {
|
|
|
|
|
- return this.assetCache.has(uri);
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ getAsset(uri) {
|
|
|
|
|
+ if (this.assetCache.has(uri)) {
|
|
|
|
|
+ return this.assetCache.get(uri);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.warn(`Asset not found in cache: ${uri}`);
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- setAsset(uri, data) {
|
|
|
|
|
- this.assetCache.set(uri, data);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ hasAsset(uri) {
|
|
|
|
|
+ return this.assetCache.has(uri);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- clearCache() {
|
|
|
|
|
- this.assetCache.clear();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ setAsset(uri, data) {
|
|
|
|
|
+ this.assetCache.set(uri, data);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ clearCache() {
|
|
|
|
|
+ this.assetCache.clear();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// TODO
|
|
// TODO
|
|
|
//
|
|
//
|
|
|
// Polish API
|
|
// 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);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+class TinyUSDZLoader extends Loader {
|
|
|
|
|
+ constructor(manager) {
|
|
|
|
|
+ super(manager);
|
|
|
|
|
+ this.native_ = null;
|
|
|
|
|
+ this.assetResolver_ = null;
|
|
|
|
|
+ this.imageCache = {};
|
|
|
|
|
+ this.textureCache = {};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化 native 模块(直接用普通 wasm,不用压缩)
|
|
|
|
|
+ async init(options = {}) {
|
|
|
|
|
+ if (!this.native_) {
|
|
|
|
|
+ console.log("Initializing TinyUSDZ native module...");
|
|
|
|
|
+ // 直接 import,不传 wasmBinary(让 Emscripten 自己 fetch /tinyusdz.wasm)
|
|
|
|
|
+ this.native_ = await initTinyUSDZNative();
|
|
|
|
|
+ if (!this.native_) {
|
|
|
|
|
+ throw new Error("Failed to initialize TinyUSDZ native module.");
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log("Native module initialized");
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- //
|
|
|
|
|
- // 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);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // load 方法保持不变(内部会调用 init)
|
|
|
|
|
+ load(url, onLoad, onProgress, onError) {
|
|
|
|
|
+ const scope = this;
|
|
|
|
|
+ const initPromise = this.native_ ? Promise.resolve() : this.init();
|
|
|
|
|
+
|
|
|
|
|
+ initPromise
|
|
|
|
|
+ .then(() => fetch(url))
|
|
|
|
|
+ .then((response) => response.arrayBuffer())
|
|
|
|
|
+ .then((usd_data) => {
|
|
|
|
|
+ const usd_binary = new Uint8Array(usd_data);
|
|
|
|
|
+ scope.parse(usd_binary, url, onLoad, onError);
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ console.error("TinyUSDZLoader error:", error);
|
|
|
|
|
+ if (onError) onError(error);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // parse 方法保持不变
|
|
|
|
|
+ parse(binary, filePath, onLoad, onError) {
|
|
|
|
|
+ if (!this.native_) {
|
|
|
|
|
+ const err = new Error("Native module not initialized");
|
|
|
|
|
+ console.error(err);
|
|
|
|
|
+ if (onError) onError(err);
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //
|
|
|
|
|
- // 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);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const usd = new this.native_.TinyUSDZLoaderNative();
|
|
|
|
|
+ const ok = usd.loadFromBinary(binary, filePath);
|
|
|
|
|
+ if (!ok) {
|
|
|
|
|
+ const err = new Error("Failed to load USD from binary", { cause: usd.error() });
|
|
|
|
|
+ if (onError) onError(err);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ onLoad(usd);
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- 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;
|
|
|
|
|
- //}
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // 其他方法(如 loadAsLayer)可以保留或根据需要删减
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export { TinyUSDZLoader, FetchAssetResolver };
|
|
export { TinyUSDZLoader, FetchAssetResolver };
|