Explorar el Código

3d模型预览

natasha hace 1 día
padre
commit
4795eb12e1
Se han modificado 1 ficheros con 220 adiciones y 3 borrados
  1. 220 3
      src/views/personal_workbench/project/ProductionResourceManage.vue

+ 220 - 3
src/views/personal_workbench/project/ProductionResourceManage.vue

@@ -109,9 +109,7 @@
                   <p class="name">
                     {{ item.name }}
                     <SvgIcon
-                      v-show="
-                        item.file_id && (type_index === 5 || type_index === 3 || type_index === 2 || type_index === 1)
-                      "
+                      v-show="item.file_id && type_index !== 0"
                       icon-class="uploadPreview"
                       size="16"
                       @click="viewDialog(item)"
@@ -213,6 +211,20 @@
     >
       <iframe v-if="visible" :src="newpath" width="100%" :height="iframeHeight" frameborder="0"></iframe>
     </el-dialog>
+    <!-- 3D模型预览 -->
+    <el-dialog
+      v-if="visible3D"
+      :visible.sync="visible3D"
+      :show-close="true"
+      :close-on-click-modal="true"
+      :modal-append-to-body="true"
+      :append-to-body="true"
+      :lock-scroll="true"
+      width="80%"
+      top="0"
+    >
+      <div style="height: 85vh" ref="three" class="three"></div>
+    </el-dialog>
   </div>
 </template>
 
@@ -235,6 +247,13 @@ import AudioLine from './components/AudioLine.vue';
 const Base64 = require('js-base64').Base64;
 import { getConfig } from '@/utils/auth';
 import { H5StartupFile, GetFileURLMap } from '@/api/app';
+import { GetFileStoreInfo } from '@/api/app';
+
+import * as THREE from 'three';
+import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
+import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
+import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
 
 export default {
   name: 'ProjectResourceManager',
@@ -322,6 +341,13 @@ export default {
       visible: false,
       newpath: '',
       iframeHeight: `${window.innerHeight - 100}px`,
+      visible3D: false,
+      scene: null,
+      camera: null,
+      renderer: null,
+      controls: null,
+      animationId: null,
+      loaded: false, // 是否加载完成
     };
   },
   created() {
@@ -627,6 +653,10 @@ export default {
           this.newpath = res.file_url;
           this.visible = true;
         });
+      } else if (this.type_index === 4) {
+        this.visible3D = true;
+        this.loadModel(file.file_id);
+        this.initThree();
       } else {
         GetFileURLMap({ file_id_list: [file.file_id] }).then(({ url_map }) => {
           this.newpath = `${this.file_preview_url}onlinePreview?url=${Base64.encode(url_map[file.file_id])}`;
@@ -634,6 +664,193 @@ export default {
         });
       }
     },
+    initThree() {
+      const container = this.$refs.three;
+      if (!container) return;
+
+      // 创建场景
+      this.scene = new THREE.Scene();
+      this.scene.background = new THREE.Color(0xf0f0f0);
+
+      // 创建相机
+      const width = container.clientWidth;
+      const height = container.clientHeight;
+      this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
+      this.camera.position.set(5, 5, 5);
+
+      // 创建渲染器
+      this.renderer = new THREE.WebGLRenderer({ antialias: true });
+      this.renderer.setSize(width, height);
+      this.renderer.shadowMap.enabled = true;
+      this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+      container.appendChild(this.renderer.domElement);
+
+      // 添加光源
+      const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
+      this.scene.add(ambientLight);
+
+      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
+      directionalLight.position.set(10, 10, 5);
+      directionalLight.castShadow = true;
+      this.scene.add(directionalLight);
+
+      // 添加控制器
+      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
+      this.controls.enableDamping = true;
+      this.controls.dampingFactor = 0.25;
+
+      // 开始渲染循环
+      this.animate();
+    },
+    animate() {
+      this.animationId = requestAnimationFrame(this.animate);
+
+      // 更新控制器
+      if (this.controls) {
+        this.controls.update();
+      }
+
+      // 更新动画混合器
+      if (this.mixer) {
+        this.mixer.update(0.016); // 假设60fps
+      }
+
+      // 渲染场景
+      if (this.renderer && this.scene && this.camera) {
+        this.renderer.render(this.scene, this.camera);
+      }
+    },
+    cleanup() {
+      // 停止动画循环
+      if (this.animationId) {
+        cancelAnimationFrame(this.animationId);
+      }
+
+      // 清理Three.js资源
+      if (this.renderer) {
+        this.renderer.dispose();
+        const container = this.$refs.three;
+        if (container && container.contains(this.renderer.domElement)) {
+          container.removeChild(this.renderer.domElement);
+        }
+      }
+
+      if (this.scene) {
+        this.scene.clear();
+      }
+    },
+    async loadModel(id) {
+      const fileStore = await GetFileStoreInfo({ file_id: id });
+      if (!fileStore || fileStore.error) {
+        console.error('模型文件加载失败:', fileStore);
+        return;
+      }
+      const modelUrl = fileStore.file_url;
+      // 根据文件扩展名选择合适的加载器
+      const extension = modelUrl.split('.').pop().toLowerCase();
+      let loader = null;
+
+      switch (extension) {
+        case 'gltf':
+        case 'glb':
+          loader = new GLTFLoader();
+          this.loadGLTF(loader, modelUrl);
+          break;
+        case 'fbx':
+          loader = new FBXLoader();
+          this.loadFBX(loader, modelUrl);
+          break;
+        case 'obj':
+          loader = new OBJLoader();
+          this.loadOBJ(loader, modelUrl);
+          break;
+        default:
+          console.error('不支持的模型格式:', extension);
+      }
+    },
+
+    loadGLTF(loader, url) {
+      loader.load(
+        url,
+        (gltf) => {
+          const model = gltf.scene;
+          this.addModelToScene(model);
+
+          // 如果模型有动画,播放第一个动画
+          if (gltf.animations && gltf.animations.length > 0) {
+            this.mixer = new THREE.AnimationMixer(model);
+            const action = this.mixer.clipAction(gltf.animations[0]);
+            action.play();
+          }
+        },
+        (progress) => {
+          if (progress.loaded >= progress.total) {
+            this.loaded = true;
+          }
+        },
+        (error) => {
+          console.error('GLTF模型加载失败:', error);
+        },
+      );
+    },
+
+    loadFBX(loader, url) {
+      loader.load(
+        url,
+        (fbx) => {
+          this.addModelToScene(fbx);
+        },
+        (progress) => {
+          if (progress.loaded >= progress.total) {
+            this.loaded = true;
+          }
+        },
+        (error) => {
+          console.error('FBX模型加载失败:', error);
+        },
+      );
+    },
+
+    loadOBJ(loader, url) {
+      loader.load(
+        url,
+        (obj) => {
+          this.addModelToScene(obj);
+        },
+        (progress) => {
+          if (progress.loaded >= progress.total) {
+            this.loaded = true;
+          }
+        },
+        (error) => {
+          console.error('OBJ模型加载失败:', error);
+        },
+      );
+    },
+
+    addModelToScene(model) {
+      // 计算模型的包围盒,用于自动调整相机位置
+      const box = new THREE.Box3().setFromObject(model);
+      const center = box.getCenter(new THREE.Vector3());
+      const size = box.getSize(new THREE.Vector3());
+
+      // 将模型移到原点
+      model.position.sub(center);
+      this.scene.add(model);
+
+      // 自动调整相机位置
+      const maxDim = Math.max(size.x, size.y, size.z);
+      const fov = this.camera.fov * (Math.PI / 180);
+      let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));
+      cameraZ *= 1.5; // 稍微远一点以便观察
+
+      this.camera.position.set(cameraZ, cameraZ, cameraZ);
+      this.camera.lookAt(0, 0, 0);
+      this.controls.target.set(0, 0, 0);
+    },
+  },
+  beforeDestroy() {
+    this.cleanup();
   },
 };
 </script>