tangning 5 月之前
父节点
当前提交
a7ab5f7345
共有 27 个文件被更改,包括 43898 次插入0 次删除
  1. 3 0
      .browserslistrc
  2. 20 0
      .eslintrc.js
  3. 27 0
      .gitignore
  4. 643 0
      LICENSE.txt
  5. 3 0
      babel.config.js
  6. 27 0
      firebase.json
  7. 32439 0
      package-lock.json
  8. 44 0
      package.json
  9. 二进制
      public/favicon.ico
  10. 60 0
      public/index.html
  11. 二进制
      public/studio_country_hall_1k.hdr
  12. 20 0
      src/App.vue
  13. 35 0
      src/components/Home.scss
  14. 304 0
      src/components/Home.ts
  15. 76 0
      src/components/Home.vue
  16. 298 0
      src/components/Home1.ts
  17. 14 0
      src/components/Home1.vue
  18. 9 0
      src/components/Window.d.ts
  19. 10 0
      src/main.ts
  20. 6 0
      src/plugins/vuetify.ts
  21. 13 0
      src/shims-tsx.d.ts
  22. 4 0
      src/shims-vue.d.ts
  23. 4 0
      src/shims-vuetify.d.ts
  24. 二进制
      src/test.usdz
  25. 42 0
      tsconfig.json
  26. 13 0
      vue.config.js
  27. 9784 0
      yarn.lock

+ 3 - 0
.browserslistrc

@@ -0,0 +1,3 @@
+> 1%
+last 2 versions
+not dead

+ 20 - 0
.eslintrc.js

@@ -0,0 +1,20 @@
+module.exports = {
+  root: true,
+  env: {
+    node: true,
+  },
+  extends: [
+    "plugin:vue/essential",
+    "eslint:recommended",
+    "@vue/typescript/recommended",
+    "@vue/prettier",
+    "@vue/prettier/@typescript-eslint",
+  ],
+  parserOptions: {
+    ecmaVersion: 2020,
+  },
+  rules: {
+    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
+    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
+  },
+};

+ 27 - 0
.gitignore

@@ -0,0 +1,27 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+public/wasm
+
+.firebaserc
+.firebase

文件差异内容过多而无法显示
+ 643 - 0
LICENSE.txt


+ 3 - 0
babel.config.js

@@ -0,0 +1,3 @@
+module.exports = {
+  presets: ["@vue/cli-plugin-babel/preset"],
+};

+ 27 - 0
firebase.json

@@ -0,0 +1,27 @@
+{
+  "hosting": {
+    "public": "dist",
+    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
+    "rewrites": [
+      {
+        "source": "**",
+        "destination": "/index.html"
+      }
+    ],
+    "headers": [
+      {
+        "regex": "/.*",
+        "headers": [
+          {
+            "key": "Cross-Origin-Embedder-Policy",
+            "value": "require-corp"
+          },
+          {
+            "key": "Cross-Origin-Opener-Policy",
+            "value": "same-origin"
+          }
+        ]
+      }
+    ]
+  }
+}

文件差异内容过多而无法显示
+ 32439 - 0
package-lock.json


+ 44 - 0
package.json

@@ -0,0 +1,44 @@
+{
+  "name": "usdz-viewer-net",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint",
+    "deploy": "npm run copydeps & npm run build & firebase deploy",
+    "copydeps": "copyfiles -u 3 \"node_modules/three-usdz-loader/external/**/*\" public/wasm"
+  },
+  "dependencies": {
+    "core-js": "^3.6.5",
+    "three": "^0.166.0",
+    "three-usdz-loader": "^1.0.9",
+    "vue": "^2.6.11",
+    "vue-class-component": "^7.2.3",
+    "vue-property-decorator": "^9.1.2",
+    "vue-router": "^4.5.0",
+    "vuetify": "^2.6.0"
+  },
+  "devDependencies": {
+    "@types/three": "^0.166.0",
+    "@typescript-eslint/eslint-plugin": "^4.18.0",
+    "@typescript-eslint/parser": "^4.18.0",
+    "@vue/cli-plugin-babel": "~4.5.13",
+    "@vue/cli-plugin-eslint": "~4.5.13",
+    "@vue/cli-plugin-typescript": "~4.5.13",
+    "@vue/cli-service": "~4.5.13",
+    "@vue/eslint-config-prettier": "^6.0.0",
+    "@vue/eslint-config-typescript": "^7.0.0",
+    "copyfiles": "^2.4.1",
+    "eslint": "^6.7.2",
+    "eslint-plugin-prettier": "^3.3.1",
+    "eslint-plugin-vue": "^6.2.2",
+    "prettier": "^2.2.1",
+    "readable-stream": "^4.0.0",
+    "sass": "~1.32.0",
+    "sass-loader": "^10.0.0",
+    "typescript": "~4.1.5",
+    "vue-cli-plugin-vuetify": "~2.4.8",
+    "vue-template-compiler": "^2.6.11",
+    "vuetify-loader": "^1.7.0"
+  }
+}

二进制
public/favicon.ico


+ 60 - 0
public/index.html

@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+    <meta name="title" content="四维看看">
+    <meta http-equiv="origin-trial" content="REGISTER GET TOKEN">
+    <meta name="description"
+      content="实景三维空间采集相机四维看看(4DKanKan)。技术核心三要素:易操作;自动化;高精度。主要应用领域为数字文博、数字地产、数字电商、数字餐饮、数字家居等。">
+    
+    <meta http-equiv="origin-trial" content="REGISTER GET TOKEN">
+    <meta
+      name="keywords"
+      content="usd, usdz, viewer, 3d, 3d model, online, online viewer, usd online viewer, usdz online viewer, pixar, universal scene description, threejs"
+    />
+    <meta name="robots" content="index, follow" />
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <meta name="language" content="English" />
+    <meta name="revisit-after" content="1 days" />
+
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
+    <title>四维看看</title>
+    <link
+      rel="stylesheet"
+      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"
+    />
+    <link
+      rel="stylesheet"
+      href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"
+    />
+    <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
+    <!-- Global site tag (gtag.js) - Google Analytics -->
+    <script
+      async
+      src="https://www.googletagmanager.com/gtag/js?id=G-H8J94WPF8V"
+    ></script>
+    <script>
+      var vConsole = new window.VConsole();
+      window.dataLayer = window.dataLayer || [];
+      function gtag() {
+        dataLayer.push(arguments);
+      }
+      gtag("js", new Date());
+
+      gtag("config", "G-H8J94WPF8V");
+    </script>
+  </head>
+  <body>
+    <noscript>
+      <strong
+        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
+        properly without JavaScript enabled. Please enable it to
+        continue.</strong
+      >
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

二进制
public/studio_country_hall_1k.hdr


+ 20 - 0
src/App.vue

@@ -0,0 +1,20 @@
+<template>
+  <v-app>
+    <v-main>
+      <Home />
+    </v-main>
+  </v-app>
+</template>
+
+<script lang="ts">
+import Vue from "vue";
+import Home from "./components/Home.vue";
+
+export default Vue.extend({
+  name: "App",
+
+  components: {
+    Home,
+  },
+});
+</script>

+ 35 - 0
src/components/Home.scss

@@ -0,0 +1,35 @@
+.drag-zone {
+  border-radius: 0.7em;
+  background: #eee;
+  padding: 2em;
+  color: rgb(78, 78, 78);
+  font-weight: 800;
+  text-align: center;
+}
+
+.drag-zone:hover {
+  background: rgb(81, 81, 81);
+  color: white;
+}
+
+.three-overlay {
+  z-index: 1;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
+.top-left {
+  z-index: 100;
+  position: absolute;
+  top: 15px;
+  left: 15px;
+}
+
+.top-right {
+  z-index: 100;
+  position: absolute;
+  top: 15px;
+  right: 15px;
+}

+ 304 - 0
src/components/Home.ts

@@ -0,0 +1,304 @@
+import { Component, Ref, Vue } from "vue-property-decorator";
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
+import { DataTexture } from "three";
+import { USDZInstance } from "three-usdz-loader/lib/USDZInstance";
+import { USDZLoader } from "three-usdz-loader";
+import { useRoute } from "vue-router";
+
+@Component
+export default class Home extends Vue {
+  @Ref("three-container") threeContainer!: HTMLElement;
+  @Ref("file") fileInput!: HTMLInputElement;
+
+  scene!: THREE.Scene;
+  camera!: THREE.PerspectiveCamera;
+  renderer!: THREE.WebGLRenderer;
+  currentFileName!: string;
+  controls!: OrbitControls;
+
+  // Tells if a model is currently visible
+  modelIsVisible = false;
+
+  // Tells if a file is loading currently
+  modelIsLoading = false;
+
+  // Dialog open or closed
+  dialog = false;
+
+  // Loaded models
+  loadedModels: USDZInstance[] = [];
+
+  // USDZ loader instance. Only one should be instantiated in the DOM scope
+  loader!: USDZLoader;
+
+  // Simple error handling
+  error: string | null = null;
+
+  // Tells if the loader has loaded with success
+  loaderReady: boolean | null = null;
+
+  async mounted(): Promise<void> {
+    // window.addEventListener("message", this.handleMessage)
+    // Setup camera
+    this.camera = new THREE.PerspectiveCamera(
+      27,
+      window.innerWidth / window.innerHeight,
+      1,
+      3500
+    );
+    this.camera.position.z = 7;
+    this.camera.position.y = 7;
+    this.camera.position.x = 0;
+
+    // Setup scene
+    this.scene = new THREE.Scene();
+    this.scene.background = new THREE.Color(0xffffff);
+
+    // Setup light
+    const ambiantLight = new THREE.AmbientLight(0x111111);
+    ambiantLight.intensity = 1;
+    this.scene.add(ambiantLight);
+
+    // Setup main scene
+    this.renderer = new THREE.WebGLRenderer({ antialias: true });
+    this.renderer.setPixelRatio(window.devicePixelRatio);
+    this.renderer.setSize(window.innerWidth, window.innerHeight);
+    this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    this.renderer.shadowMap.enabled = false;
+    this.renderer.shadowMap.type = THREE.VSMShadowMap;
+
+    // Setup cubemap for reflection
+    await new Promise((resolve) => {
+      const pmremGenerator = new THREE.PMREMGenerator(this.renderer);
+      pmremGenerator.compileCubemapShader();
+      new RGBELoader().load(
+        "studio_country_hall_1k.hdr",
+        (texture: DataTexture) => {
+          const hdrRenderTarget = pmremGenerator.fromEquirectangular(texture);
+
+          texture.mapping = THREE.EquirectangularReflectionMapping;
+          texture.needsUpdate = true;
+          window.envMap = hdrRenderTarget.texture;
+          hdrRenderTarget.texture.colorSpace = THREE.LinearSRGBColorSpace;
+          resolve(true);
+        }
+      );
+    });
+
+    //Add the canvas to the document
+    this.threeContainer.appendChild(this.renderer.domElement);
+
+    // Setup navigation
+    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
+    this.controls.update();
+
+    // Setup main animation update loop
+    this.animate();
+
+    // Setup the USDZ loader
+    this.loader = new USDZLoader("./wasm");
+    const obj = this.getQueryParams(window.location.search);
+    const fileName = (obj.m || "test") + ".usdz";
+    // Setup windows events
+    window.addEventListener("resize", this.onWindowResize);
+    this.getUrlAsFile(fileName, fileName, (file: File) => {
+      console.log("File object:", file);
+      this.loadFile(file);
+      // 现在你可以使用 file 对象进行操作
+    });
+  }
+
+  /**
+   * Main update loop
+   */
+  async animate(): Promise<void> {
+    const secs = new Date().getTime() / 1000;
+    await new Promise((resolve) => setTimeout(resolve, 10));
+
+    // Update all models animations (in our exemple there is only one model at a time)
+    for (const loadedModel of this.loadedModels) {
+      loadedModel.update(secs);
+    }
+
+    this.renderer.render(this.scene, this.camera);
+    requestAnimationFrame(this.animate.bind(null));
+  }
+  handleMessage(event: any): void {
+    console.log("handleMessage");
+    (window as any).webkit &&
+      (window as any).webkit.messageHandlers &&
+      (window as any).webkit.messageHandlers.inappbrowserbridge.postMessage(
+        "loading",
+        true
+      );
+    window.parent && window.parent.postMessage({ loading: true }, "*");
+  }
+  /**
+   * Load a USDZ file in the scene
+   * @param file
+   * @returns
+   */
+  async loadFile(file: File): Promise<void> {
+    // Prevents multiple loadings in parallel
+    if (this.modelIsLoading) {
+      return;
+    }
+
+    // Notice model is now loading
+    this.modelIsLoading = true;
+
+    // Reset any previous error
+    this.error = null;
+
+    // Clearup any previsouly loaded model
+    // We could technically load multiple files by removing this for loop
+    for (const el of this.loadedModels) {
+      el.clear();
+    }
+    this.loadedModels = [];
+
+    // Create the ThreeJs Group in which the loaded USDZ model will be placed
+    const group = new THREE.Group();
+    this.scene.add(group);
+
+    // Load file and catch any error to show the user
+    try {
+      const loadedModel = await this.loader.loadFile(file, group);
+      this.loadedModels.push(loadedModel);
+    } catch (e) {
+      this.error = e as string;
+      console.error("An error occured when trying to load the model" + e);
+      this.modelIsLoading = false;
+      return;
+    }
+
+    // Fits the camera to match the loaded model
+    const allContainers = this.loadedModels.map((el: USDZInstance) => {
+      return el.getGroup();
+    });
+    this.fitCamera(this.camera, this.controls, allContainers);
+
+    // Notice end
+    this.modelIsLoading = false;
+    this.modelIsVisible = true;
+    console.log("Model loaded 渲染完毕");
+    this.handleMessage("loading");
+    // window.parent && window.parent.postMessage("loading", "*");
+  }
+
+  /**
+   * Fits the camera view to the imported objects
+   */
+  fitCamera(
+    camera: THREE.PerspectiveCamera,
+    controls: OrbitControls,
+    selection: THREE.Group[],
+    fitOffset = 1.5
+  ): void {
+    const cam = camera as THREE.PerspectiveCamera;
+    const size = new THREE.Vector3();
+    const center = new THREE.Vector3();
+    const box = new THREE.Box3();
+
+    box.makeEmpty();
+    for (const object of selection) {
+      box.expandByObject(object);
+    }
+
+    box.getSize(size);
+    box.getCenter(center);
+
+    const maxSize = Math.max(size.x, size.y, size.z);
+    const fitHeightDistance =
+      maxSize / (2 * Math.atan((Math.PI * cam.fov) / 360));
+    const fitWidthDistance = fitHeightDistance / cam.aspect;
+    const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
+
+    const direction = controls.target
+      .clone()
+      .sub(cam.position)
+      .normalize()
+      .multiplyScalar(distance);
+
+    controls.maxDistance = distance * 10;
+    controls.target.copy(center);
+
+    cam.near = distance / 100;
+    cam.far = distance * 100;
+    cam.updateProjectionMatrix();
+
+    camera.position.copy(controls.target).sub(direction);
+
+    controls.update();
+  }
+
+  onWindowResize(): void {
+    this.camera.aspect = window.innerWidth / window.innerHeight;
+    this.camera.updateProjectionMatrix();
+    this.renderer.setSize(window.innerWidth, window.innerHeight);
+  }
+  onChange(): void {
+    if (this.fileInput.files != null) {
+      this.handleFilesUpload(this.fileInput.files);
+    }
+  }
+  onClickDragZone(): void {
+    this.fileInput.click();
+  }
+  getUrlAsFile(url = "test.usdz", fileName = "test.usdz", callback: any): void {
+    const xhr = new XMLHttpRequest();
+    // xhr.open("GET", "http://localhost:8888/test.usdz", true);
+    xhr.open("GET", "/20241202dome/" + url, true);
+    xhr.setRequestHeader("Cross-Origin-Embedder-Policy", "require-corp");
+    xhr.setRequestHeader("Cross-Origin-Opener-Policy", "same-origin");
+    xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
+    xhr.responseType = "blob";
+
+    xhr.onload = function () {
+      console.log("onload:onload", xhr.response);
+      if (xhr.status === 200) {
+        const blob = xhr.response;
+        const file = new File([blob], fileName, { type: blob.type });
+        callback(file);
+      } else {
+        console.error("Error fetching the file:", xhr.statusText);
+      }
+    };
+
+    xhr.onerror = function () {
+      console.error("Error fetching the file:", xhr.statusText);
+    };
+
+    xhr.send();
+  }
+  dragover(event: DragEvent): void {
+    event.preventDefault();
+  }
+  drop(event: DragEvent): void {
+    event.preventDefault();
+    if (event.dataTransfer == null) {
+      console.error("Files are null");
+      return;
+    }
+    this.handleFilesUpload(event.dataTransfer.files);
+  }
+  handleFilesUpload(files: FileList): void {
+    console.log("handleFilesUpload", files);
+    this.loadFile(files[0]);
+  }
+  getQueryParams(url: string): any {
+    const paramArr = url.slice(url.indexOf("?") + 1).split("&");
+    const params = {
+      m: "",
+    };
+    paramArr.map((param) => {
+      const [key, val] = param.split("=");
+      if (key == "m") {
+        params.m = decodeURIComponent(val);
+      }
+    });
+    return params;
+  }
+}

+ 76 - 0
src/components/Home.vue

@@ -0,0 +1,76 @@
+<template>
+  <div
+    class="three-container"
+    @dragover="dragover"
+    @drop="drop"
+    ref="three-container"
+  >
+    <div class="three-overlay">
+      <div v-if="error != null" class="mb-3" style="color: red; center;">
+        An error occured when loading the USDZ file. Maybe this file is not
+        supported or the loader is not supported on this device.
+      </div>
+      <input
+        type="file"
+        multiple
+        v-show="false"
+        name="fields[assetsFieldHandle][]"
+        class="w-px h-px opacity-0 overflow-hidden absolute"
+        @change="onChange"
+        ref="file"
+        accept=".usdz"
+      />
+      <div v-if="!modelIsVisible" class="drag-zone" @click="onClickDragZone">
+        <div
+          class="flex w-full h-screen items-center justify-center text-center"
+          id="app"
+        >
+          <div>加载中</div>
+        </div>
+      </div>
+    </div>
+    <!-- <div class="top-left" @click="handleMessage">
+      <v-btn
+        class="mx-2"
+        small
+        outlined
+        fab
+        dark
+        color="indigo"
+      >
+        <v-icon dark> mdi-help </v-icon>
+      </v-btn>
+    </div> -->
+    <div v-if="modelIsVisible" class="top-right" @click="onClickDragZone">
+      <v-btn outlined rounded class="mx-2" dark color="indigo">
+        Load another model
+      </v-btn>
+    </div>
+    <v-dialog v-model="dialog" width="500">
+      <v-card>
+        <v-card-title class="text-h5">
+          Information about the player
+        </v-card-title>
+
+        <v-card-text class="mt-2">
+          This player is based on the
+          <a href="https://www.npmjs.com/package/three-usdz-loader"
+            >three-usdz-loader</a
+          >
+          package<br />
+          Contact: contact@usdz-viewer.net
+        </v-card-text>
+
+        <v-divider></v-divider>
+
+        <v-card-actions>
+          <v-spacer></v-spacer>
+          <v-btn color="primary" text @click="dialog = false"> OK </v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
+  </div>
+</template>
+
+<script src="./Home.ts"></script>
+<style src="./Home.scss" lang="scss"></style>

+ 298 - 0
src/components/Home1.ts

@@ -0,0 +1,298 @@
+import { Component, Ref, Vue } from "vue-property-decorator";
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
+import { DataTexture } from "three";
+import { USDZInstance } from "three-usdz-loader/lib/USDZInstance";
+import { USDZLoader } from "three-usdz-loader";
+import { useRoute } from "vue-router";
+
+@Component
+export default class Home extends Vue {
+  @Ref("three-container") threeContainer!: HTMLElement;
+  @Ref("file") fileInput!: HTMLInputElement;
+
+  scene!: THREE.Scene;
+  camera!: THREE.PerspectiveCamera;
+  renderer!: THREE.WebGLRenderer;
+  currentFileName!: string;
+  controls!: OrbitControls;
+
+  // Tells if a model is currently visible
+  modelIsVisible = false;
+
+  // Tells if a file is loading currently
+  modelIsLoading = false;
+
+  // Dialog open or closed
+  dialog = false;
+
+  // Loaded models
+  loadedModels: USDZInstance[] = [];
+
+  // USDZ loader instance. Only one should be instantiated in the DOM scope
+  loader!: USDZLoader;
+
+  // Simple error handling
+  error: string | null = null;
+
+  // Tells if the loader has loaded with success
+  loaderReady: boolean | null = null;
+
+  async mounted(): Promise<void> {
+    window.addEventListener("message", this.handleMessage)
+    // Setup camera
+    // this.camera = new THREE.PerspectiveCamera(
+    //   27,
+    //   window.innerWidth / window.innerHeight,
+    //   1,
+    //   3500
+    // );
+    // this.camera.position.z = 7;
+    // this.camera.position.y = 7;
+    // this.camera.position.x = 0;
+
+    // // Setup scene
+    // this.scene = new THREE.Scene();
+    // this.scene.background = new THREE.Color(0xffffff);
+
+    // // Setup light
+    // const ambiantLight = new THREE.AmbientLight(0x111111);
+    // ambiantLight.intensity = 1;
+    // this.scene.add(ambiantLight);
+
+    // // Setup main scene
+    // this.renderer = new THREE.WebGLRenderer({ antialias: true });
+    // this.renderer.setPixelRatio(window.devicePixelRatio);
+    // this.renderer.setSize(window.innerWidth, window.innerHeight);
+    // this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    // this.renderer.shadowMap.enabled = false;
+    // this.renderer.shadowMap.type = THREE.VSMShadowMap;
+
+    // // Setup cubemap for reflection
+    // await new Promise((resolve) => {
+    //   const pmremGenerator = new THREE.PMREMGenerator(this.renderer);
+    //   pmremGenerator.compileCubemapShader();
+    //   new RGBELoader().load(
+    //     "studio_country_hall_1k.hdr",
+    //     (texture: DataTexture) => {
+    //       const hdrRenderTarget = pmremGenerator.fromEquirectangular(texture);
+
+    //       texture.mapping = THREE.EquirectangularReflectionMapping;
+    //       texture.needsUpdate = true;
+    //       window.envMap = hdrRenderTarget.texture;
+    //       hdrRenderTarget.texture.colorSpace = THREE.LinearSRGBColorSpace;
+    //       resolve(true);
+    //     }
+    //   );
+    // });
+
+    // //Add the canvas to the document
+    // this.threeContainer.appendChild(this.renderer.domElement);
+
+    // // Setup navigation
+    // this.controls = new OrbitControls(this.camera, this.renderer.domElement);
+    // this.controls.update();
+
+    // // Setup main animation update loop
+    // this.animate();
+
+    // // Setup the USDZ loader
+    // this.loader = new USDZLoader("./wasm");
+    // const obj = this.getQueryParams(window.location.search);
+    // const fileName = (obj.m || "test") + ".usdz";
+    // // Setup windows events
+    // window.addEventListener("resize", this.onWindowResize);
+    // this.getUrlAsFile(fileName, fileName, (file: File) => {
+    //   console.log("File object:", file);
+    //   this.loadFile(file);
+    //   // 现在你可以使用 file 对象进行操作
+    // });
+  }
+
+  /**
+   * Main update loop
+   */
+  async animate(): Promise<void> {
+    const secs = new Date().getTime() / 1000;
+    await new Promise((resolve) => setTimeout(resolve, 10));
+
+    // Update all models animations (in our exemple there is only one model at a time)
+    for (const loadedModel of this.loadedModels) {
+      loadedModel.update(secs);
+    }
+
+    this.renderer.render(this.scene, this.camera);
+    requestAnimationFrame(this.animate.bind(null));
+  }
+  handleMessage(event: MessageEvent): void {
+    console.log('handleMessage', event.data);
+    // console.log("handleMessage", window);
+    // window.parent && window.parent.postMessage({loading: true}, "*");
+  }
+  /**
+   * Load a USDZ file in the scene
+   * @param file
+   * @returns
+   */
+  async loadFile(file: File): Promise<void> {
+    // Prevents multiple loadings in parallel
+    if (this.modelIsLoading) {
+      return;
+    }
+
+    // Notice model is now loading
+    this.modelIsLoading = true;
+
+    // Reset any previous error
+    this.error = null;
+
+    // Clearup any previsouly loaded model
+    // We could technically load multiple files by removing this for loop
+    for (const el of this.loadedModels) {
+      el.clear();
+    }
+    this.loadedModels = [];
+
+    // Create the ThreeJs Group in which the loaded USDZ model will be placed
+    const group = new THREE.Group();
+    this.scene.add(group);
+
+    // Load file and catch any error to show the user
+    try {
+      const loadedModel = await this.loader.loadFile(file, group);
+      this.loadedModels.push(loadedModel);
+    } catch (e) {
+      this.error = e as string;
+      console.error("An error occured when trying to load the model" + e);
+      this.modelIsLoading = false;
+      return;
+    }
+
+    // Fits the camera to match the loaded model
+    const allContainers = this.loadedModels.map((el: USDZInstance) => {
+      return el.getGroup();
+    });
+    this.fitCamera(this.camera, this.controls, allContainers);
+
+    // Notice end
+    this.modelIsLoading = false;
+    this.modelIsVisible = true;
+    console.log("Model loaded 渲染完毕");
+    window.parent && window.parent.postMessage({loading: true}, "*");
+  }
+
+  /**
+   * Fits the camera view to the imported objects
+   */
+  fitCamera(
+    camera: THREE.PerspectiveCamera,
+    controls: OrbitControls,
+    selection: THREE.Group[],
+    fitOffset = 1.5
+  ): void {
+    const cam = camera as THREE.PerspectiveCamera;
+    const size = new THREE.Vector3();
+    const center = new THREE.Vector3();
+    const box = new THREE.Box3();
+
+    box.makeEmpty();
+    for (const object of selection) {
+      box.expandByObject(object);
+    }
+
+    box.getSize(size);
+    box.getCenter(center);
+
+    const maxSize = Math.max(size.x, size.y, size.z);
+    const fitHeightDistance =
+      maxSize / (2 * Math.atan((Math.PI * cam.fov) / 360));
+    const fitWidthDistance = fitHeightDistance / cam.aspect;
+    const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
+
+    const direction = controls.target
+      .clone()
+      .sub(cam.position)
+      .normalize()
+      .multiplyScalar(distance);
+
+    controls.maxDistance = distance * 10;
+    controls.target.copy(center);
+
+    cam.near = distance / 100;
+    cam.far = distance * 100;
+    cam.updateProjectionMatrix();
+
+    camera.position.copy(controls.target).sub(direction);
+
+    controls.update();
+  }
+
+  onWindowResize(): void {
+    this.camera.aspect = window.innerWidth / window.innerHeight;
+    this.camera.updateProjectionMatrix();
+    this.renderer.setSize(window.innerWidth, window.innerHeight);
+  }
+  onChange(): void {
+    if (this.fileInput.files != null) {
+      this.handleFilesUpload(this.fileInput.files);
+    }
+  }
+  onClickDragZone(): void {
+    this.fileInput.click();
+  }
+  getUrlAsFile(url = "test.usdz", fileName = "test.usdz", callback: any): void {
+    const xhr = new XMLHttpRequest();
+    // xhr.open("GET", "http://localhost:8888/test.usdz", true);
+    xhr.open("GET", "https://xinjiapo-aws-test.4dkankan.com/20241202dome/" + url, true);
+    xhr.setRequestHeader("Cross-Origin-Embedder-Policy", "require-corp");
+    xhr.setRequestHeader("Cross-Origin-Opener-Policy", "same-origin");
+    xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
+    xhr.responseType = "blob";
+
+    xhr.onload = function () {
+      console.log("onload:onload", xhr.response);
+      if (xhr.status === 200) {
+        const blob = xhr.response;
+        const file = new File([blob], fileName, { type: blob.type });
+        callback(file);
+      } else {
+        console.error("Error fetching the file:", xhr.statusText);
+      }
+    };
+
+    xhr.onerror = function () {
+      console.error("Error fetching the file:", xhr.statusText);
+    };
+
+    xhr.send();
+  }
+  dragover(event: DragEvent): void {
+    event.preventDefault();
+  }
+  drop(event: DragEvent): void {
+    event.preventDefault();
+    if (event.dataTransfer == null) {
+      console.error("Files are null");
+      return;
+    }
+    this.handleFilesUpload(event.dataTransfer.files);
+  }
+  handleFilesUpload(files: FileList): void {
+    console.log("handleFilesUpload", files);
+    this.loadFile(files[0]);
+  }
+  getQueryParams(url: string): any {
+    const paramArr = url.slice(url.indexOf("?") + 1).split("&");
+    const params = {
+      m: "",
+    };
+    paramArr.map((param) => {
+      const [key, val] = param.split("=");
+      if (key == "m") {
+        params.m = decodeURIComponent(val);
+      }
+    });
+    return params;
+  }
+}

+ 14 - 0
src/components/Home1.vue

@@ -0,0 +1,14 @@
+<template>
+  <div
+    class="three-container"
+    @dragover="dragover"
+    @drop="drop"
+    ref="three-container"
+  >
+    <iframe style="width: 100%;height: 100vh" src="/20241202dome/index.html?m=asset" frameborder="0"></iframe>
+  </div>
+</template>
+
+<script src="./Home1.ts"></script>
+<style src="./Home.scss" lang="scss"></style>
+

+ 9 - 0
src/components/Window.d.ts

@@ -0,0 +1,9 @@
+import { USDModule } from "./USDModule";
+
+export {};
+
+declare global {
+  interface Window {
+    envMap: THREE.Texture;
+  }
+}

+ 10 - 0
src/main.ts

@@ -0,0 +1,10 @@
+import Vue from "vue";
+import App from "./App.vue";
+import vuetify from "./plugins/vuetify";
+
+Vue.config.productionTip = false;
+
+new Vue({
+  vuetify,
+  render: (h) => h(App),
+}).$mount("#app");

+ 6 - 0
src/plugins/vuetify.ts

@@ -0,0 +1,6 @@
+import Vue from "vue";
+import Vuetify from "vuetify/lib/framework";
+
+Vue.use(Vuetify);
+
+export default new Vuetify({});

+ 13 - 0
src/shims-tsx.d.ts

@@ -0,0 +1,13 @@
+import Vue, { VNode } from "vue";
+
+declare global {
+  namespace JSX {
+    // tslint:disable no-empty-interface
+    interface Element extends VNode {}
+    // tslint:disable no-empty-interface
+    interface ElementClass extends Vue {}
+    interface IntrinsicElements {
+      [elem: string]: any;
+    }
+  }
+}

+ 4 - 0
src/shims-vue.d.ts

@@ -0,0 +1,4 @@
+declare module "*.vue" {
+  import Vue from "vue";
+  export default Vue;
+}

+ 4 - 0
src/shims-vuetify.d.ts

@@ -0,0 +1,4 @@
+declare module "vuetify/lib/framework" {
+  import Vuetify from "vuetify";
+  export default Vuetify;
+}

二进制
src/test.usdz


+ 42 - 0
tsconfig.json

@@ -0,0 +1,42 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "strict": true,
+    "jsx": "preserve",
+    "importHelpers": true,
+    "moduleResolution": "node",
+    "experimentalDecorators": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
+    "sourceMap": true,
+    "baseUrl": ".",
+    "types": [
+      "webpack-env"
+    ],
+    "paths": {
+      "@/*": [
+        "src/*"
+      ]
+    },
+    "lib": [
+      "esnext",
+      "dom",
+      "dom.iterable",
+      "scripthost"
+    ]
+  },
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue",
+    "*.json",
+    "tests/**/*.ts",
+    "tests/**/*.tsx"
+  ],
+  "exclude": [
+    "node_modules",
+    "src/**/*.js"
+  ]
+}

+ 13 - 0
vue.config.js

@@ -0,0 +1,13 @@
+module.exports = {
+  publicPath: "./",
+  configureWebpack: {
+    devServer: {
+      headers: {
+        "Access-Control-Allow-Origin": "*",
+        "Cross-Origin-Embedder-Policy": "require-corp",
+        "Cross-Origin-Opener-Policy": "same-origin",
+      },
+    },
+  },
+  transpileDependencies: ["vuetify"],
+};

文件差异内容过多而无法显示
+ 9784 - 0
yarn.lock