lanxin 2 viikkoa sitten
vanhempi
commit
ec24ad2a6f
3 muutettua tiedostoa jossa 99 lisäystä ja 301 poistoa
  1. 5 5
      src/scene.jsx
  2. 1 0
      src/util.js
  3. 93 296
      usdzLoader/TinyUSDZLoader.js

+ 5 - 5
src/scene.jsx

@@ -2,7 +2,8 @@ import  { Suspense, useEffect, useMemo, useRef, useState, useCallback } from "re
 import {  useThree, useFrame } from "@react-three/fiber";
 import { OrbitControls,  useGLTF, Bounds, useFBX, useAnimations } from "@react-three/drei";
 import * as THREE from "three";
-
+import { useLoader } from '@react-three/fiber';
+import {TinyUSDZLoader} from '../usdzLoader/TinyUSDZLoader.js'
 /**
  * --- 1. 模型加载模块 (Model Loading) ---
  * 负责不同格式模型的加载 (GLTF, FBX) 以及动画播放
@@ -179,10 +180,9 @@ function FBXModel({ url, ...props }) {
 }
 
 // 预留 USDZ 支持 (iOS ARQuickLook 格式)
-function USDZModel({ url }) {
-  // 注意:useGLTF 默认不支持 .usdz,需配合特定 loader 或其他库
-  // 这里仅作占位,方便后续扩展
-  return null;
+function USDZModel({ url, ...props }) {
+  // const usdz = useLoader(TinyUSDZLoader, url); // 返回 THREE.Group 或 Scene
+  // return <primitive object={usdz} {...props} />;
 }
 
 // 统一模型加载器

+ 1 - 0
src/util.js

@@ -0,0 +1 @@
+export function name(params) {}

+ 93 - 296
usdzLoader/TinyUSDZLoader.js

@@ -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.
-import initTinyUSDZNative from './tinyusdz.js';
-
+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;
-        }
+  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
 //
 // 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 };