Prechádzať zdrojové kódy

Merge branch 'master' of http://39.97.59.228:8013/lxh/gas-injection

lxh 1 mesiac pred
rodič
commit
84ce82834d

+ 2 - 1
package.json

@@ -218,5 +218,6 @@
         "three/examples/jsm/postprocessing/UnrealBloomPass.js"
       ]
     }
-  }
+  },
+  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
 }

BIN
public/model/hdr/studio_small_09_1k.hdr


BIN
public/texture/1-1.png


BIN
public/texture/2-1.png


BIN
public/texture/2-2.png


BIN
public/texture/3-2.png


BIN
public/texture/4-2.png


+ 4 - 4
src/utils/threejs/useThree.ts

@@ -69,7 +69,7 @@ class UseThree {
     // 初始化场景
     this.initScene();
     // 初始化环境光
-    // this.initLight();
+    this.initLight();
     // 初始化相机
     this.initCamera();
     //初始化渲染器
@@ -106,9 +106,9 @@ class UseThree {
   }
 
   initLight() {
-    // const light = new THREE.AmbientLight(0xffffff, 1);
-    // light.position.set(0, 1000, 1000);
-    // (this.scene as THREE.Scene).add(light);
+    const light = new THREE.AmbientLight(0xffffff, 1);
+    light.position.set(0, 1000, 1000);
+    (this.scene as THREE.Scene).add(light);
   }
 
   initCamera() {

+ 9 - 1
src/views/vent/home/configurable/components/three3D.vue

@@ -31,6 +31,14 @@
     const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
     directionalLight.position.set(-110, 150, 647);
     modalGroup?.add(directionalLight);
+
+    const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1);
+    directionalLight.position.set(110, 150, 647);
+    modalGroup?.add(directionalLight1);
+
+    const directionalLight3 = new THREE.DirectionalLight(0xffffff, 1);
+    directionalLight.position.set(10, 150, 647);
+    modalGroup?.add(directionalLight3);
   };
   const setModelType = () => {
     return new Promise(async (resolve) => {
@@ -112,7 +120,7 @@
   onMounted(async () => {
     modal = new UseThree('#three3D', '');
     if (modal) {
-      modal.setEnvMap('test1.hdr');
+      // modal.setEnvMap('studio_small_09_1k.hdr');
       modal.renderer.toneMappingExposure = 1.0;
       modal.renderer.sortObjects = true;
       modal.orbitControls.update();

+ 234 - 219
src/views/vent/home/configurable/gasInjection.vue

@@ -9,17 +9,32 @@
       </div>
       <template v-if="menuName == 'zjm'">
         <div class="main-status"> 运行中 </div>
-        <ModuleGasInject v-for="cfg in configs" :key="cfg.deviceType" :show-style="cfg.showStyle"
-          :module-data="cfg.moduleData" :module-name="cfg.moduleName" :device-type="cfg.deviceType" :data="data"
-          :visible="true" :visible-detail="cfg.showDetail" />
+        <ModuleGasInject
+          v-for="cfg in configs"
+          :key="cfg.deviceType"
+          :show-style="cfg.showStyle"
+          :module-data="cfg.moduleData"
+          :module-name="cfg.moduleName"
+          :device-type="cfg.deviceType"
+          :data="data"
+          :visible="true"
+          :visible-detail="cfg.showDetail"
+        />
       </template>
       <template v-else>
-        <ModuleGasInject v-for="cfg in configs" :key="cfg.deviceType" :show-style="cfg.showStyle"
-          :module-data="cfg.moduleData" :module-name="cfg.moduleName" :device-type="cfg.deviceType" :data="data"
-          :visible="true" :visible-detail="cfg.showDetail" />
+        <ModuleGasInject
+          v-for="cfg in configs"
+          :key="cfg.deviceType"
+          :show-style="cfg.showStyle"
+          :module-data="cfg.moduleData"
+          :module-name="cfg.moduleName"
+          :device-type="cfg.deviceType"
+          :data="data"
+          :visible="true"
+          :visible-detail="cfg.showDetail"
+        />
       </template>
-      <div
-        :class="{ 'vent-modal': menuName == 'zjm', 'vent-modal-1': menuName == 'syq' || menuName == 'zqxt' || menuName == 'ccxt' }">
+      <div :class="{ 'vent-modal': menuName == 'zjm', 'vent-modal-1': menuName == 'syq' || menuName == 'zqxt' || menuName == 'ccxt' }">
         <div class="modal-box">
           <Three3D ref="three3D" :modalName="'zhuqi'" class="modal-3d" @success="initModalAnimate" />
         </div>
@@ -30,262 +45,262 @@
   </div>
 </template>
 <script lang="ts" setup>
-import { onMounted, onUnmounted, ref, computed, nextTick } from 'vue';
-import { useInitConfigs, useInitPage } from './hooks/useInit';
-import ModuleGasInject from './components/ModuleGasInject.vue';
-import navMenu from './components/gasInject/navMenu.vue';
-import Three3D from './components/three3D.vue';
-import { getHomeData, getSystemApi } from './configurable.api';
-// import { useRoute } from 'vue-router';
-import { testConfigGasInject, testConfigGasInjectZq, testConfigGasInjectCc, testConfigGasInjectSy } from './configurable.data';
-import { animateCamera } from '/@/utils/threejs/util';
-// import { modalAnimate } from './threejs/gasInjection';
-// import * as dat from 'dat.gui';
-// const gui = new dat.GUI();
-// gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
+  import { onMounted, onUnmounted, ref, computed, nextTick } from 'vue';
+  import { useInitConfigs, useInitPage } from './hooks/useInit';
+  import ModuleGasInject from './components/ModuleGasInject.vue';
+  import navMenu from './components/gasInject/navMenu.vue';
+  import Three3D from './components/three3D.vue';
+  import { getHomeData, getSystemApi } from './configurable.api';
+  // import { useRoute } from 'vue-router';
+  import { testConfigGasInject, testConfigGasInjectZq, testConfigGasInjectCc, testConfigGasInjectSy } from './configurable.data';
+  import { animateCamera } from '/@/utils/threejs/util';
+  import { modalAnimate } from './threejs/gasInjection';
+  // import * as dat from 'dat.gui';
+  // const gui = new dat.GUI();
+  // gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
 
-const { configs, fetchConfigs } = useInitConfigs();
-const { mainTitle, data, updateData } = useInitPage('注气驱替智能管控系统');
-const three3D = ref(null);
-let interval: number | undefined;
-let menuName = ref('zjm');
+  const { configs, fetchConfigs } = useInitConfigs();
+  const { mainTitle, data, updateData } = useInitPage('注气驱替智能管控系统');
+  const three3D = ref(null);
+  let interval: number | undefined;
+  let menuName = ref('zjm');
 
-//选项切换
-function toggleMenu(param) {
-  menuName.value = param;
-  switch (menuName.value) {
-    case 'zjm':
-      configs.value = testConfigGasInject;
-      break;
-    case 'syq':
-      configs.value = testConfigGasInjectSy;
-      break;
-    case 'zqxt':
-      configs.value = testConfigGasInjectZq;
-      break;
-    case 'ccxt':
-      configs.value = testConfigGasInjectCc;
-      break;
-  }
+  //选项切换
+  function toggleMenu(param) {
+    menuName.value = param;
+    switch (menuName.value) {
+      case 'zjm':
+        configs.value = testConfigGasInject;
+        break;
+      case 'syq':
+        configs.value = testConfigGasInjectSy;
+        break;
+      case 'zqxt':
+        configs.value = testConfigGasInjectZq;
+        break;
+      case 'ccxt':
+        configs.value = testConfigGasInjectCc;
+        break;
+    }
 
-  // 刷新/
-  nextTick(async () => {
-    three3D.value?.resizeRenderer();
-    const modal = three3D.value.getModal();
-    if (modal) {
-      const oldCamera = modal.camera;
-      const oldCameraPosition = { x: oldCamera.position.x, y: oldCamera.position.y, z: oldCamera.position.z };
-      if (param == 'zqxt') {
-        const newP = { x: -26.587483577770445, y: -1.885170491810538, z: 39.104309663143255 };
-        const newT = { x: 10.919881491980828, y: -4.7228274957026946, z: 38.87645926712004 };
-        await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, newP, newT, modal);
-      } else if (param == 'zjm') {
-        const newP = { x: -45.69228602978097, y: 49.59721939545517, z: 2.6454258202266985 };
-        const newT = { x: 9.289291846942458, y: -21.608842010051386, z: 2.7675348357947906 };
-        await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, newP, newT, modal);
-      } else {
-        const newP = { x: -43.18945276107877, y: 40.44347648044, z: 0.059975838354020664 };
-        const newT = { x: 6.431421158296241, y: -23.819993211059913, z: 0.17017939135402457 };
-        await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, newP, newT, modal);
+    // 刷新/
+    nextTick(async () => {
+      three3D.value?.resizeRenderer();
+      const modal = three3D.value.getModal();
+      if (modal) {
+        const oldCamera = modal.camera;
+        const oldCameraPosition = { x: oldCamera.position.x, y: oldCamera.position.y, z: oldCamera.position.z };
+        if (param == 'zqxt') {
+          const newP = { x: -29.4124614570094, y: -1.6093280622283133, z: 39.51639267244655 };
+          const newT = { x: 11.205344322178817, y: -1.7174801102124406, z: 40.19437751610255 };
+          await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, newP, newT, modal);
+        } else if (param == 'zjm') {
+          const newP = { x: -45.69228602978097, y: 49.59721939545517, z: 2.6454258202266985 };
+          const newT = { x: 9.289291846942458, y: -21.608842010051386, z: 2.7675348357947906 };
+          await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, newP, newT, modal);
+        } else {
+          const newP = { x: -43.18945276107877, y: 40.44347648044, z: 0.059975838354020664 };
+          const newT = { x: 6.431421158296241, y: -23.819993211059913, z: 0.17017939135402457 };
+          await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, newP, newT, modal);
+        }
       }
-    }
-  });
-}
+    });
+  }
 
-function initModalAnimate(modal) {
-  console.log('初始化模型', modal);
-  modal.isRender = true;
+  function initModalAnimate(modal) {
+    console.log('初始化模型', modal);
+    modal.isRender = true;
 
-  // modalAnimate(modal);
-}
+    modalAnimate(modal);
+  }
 
-onMounted(() => {
-  fetchConfigs('gas_injection').then(() => {
-    configs.value = testConfigGasInject;
-    //  updateEnhancedConfigs(configs.value);
-    getSystemApi({systemID: "2036323791827165185",}).then(updateData);
-  });
+  onMounted(() => {
+    fetchConfigs('gas_injection').then(() => {
+      configs.value = testConfigGasInject;
+      //  updateEnhancedConfigs(configs.value);
+      getSystemApi({ systemID: '2036323791827165185' }).then(updateData);
+    });
 
-  setInterval(() => {
-    getSystemApi({systemID: "2036323791827165185",}).then(updateData);
-  }, 60000);
-});
+    setInterval(() => {
+      getSystemApi({ systemID: '2036323791827165185' }).then(updateData);
+    }, 60000);
+  });
 
-onUnmounted(() => {
-  clearInterval(interval);
-});
+  onUnmounted(() => {
+    clearInterval(interval);
+  });
 </script>
 
 <style lang="less" scoped>
-@import '/@/design/theme.less';
+  @import '/@/design/theme.less';
+
+  @{theme-deepblue} {
+    .company-home {
+      --image-modal-top: url('@/assets/images/gasInjection/1-1.png');
+      --image-modal-status: url('@/assets/images/gasInjection/3-1.png');
+      --image-modal-center: url('@/assets/images/gasInjection/1-2.png');
+    }
+  }
 
-@{theme-deepblue} {
   .company-home {
     --image-modal-top: url('@/assets/images/gasInjection/1-1.png');
     --image-modal-status: url('@/assets/images/gasInjection/3-1.png');
     --image-modal-center: url('@/assets/images/gasInjection/1-2.png');
-  }
-}
-
-.company-home {
-  --image-modal-top: url('@/assets/images/gasInjection/1-1.png');
-  --image-modal-status: url('@/assets/images/gasInjection/3-1.png');
-  --image-modal-center: url('@/assets/images/gasInjection/1-2.png');
-  width: 100%;
-  height: 100%;
-  color: @white;
-  position: relative;
-  background: url('@/assets/images/vent/homeNew/bg.png') no-repeat center;
-
-  .top-bg {
     width: 100%;
-    height: 66px;
-    background: var(--image-modal-top) no-repeat center;
-    background-size: 100% 100%;
-    position: absolute;
-    z-index: 1;
+    height: 100%;
+    color: @white;
+    position: relative;
+    background: url('@/assets/images/vent/homeNew/bg.png') no-repeat center;
 
-    .main-title {
+    .top-bg {
+      width: 100%;
       height: 66px;
-      font-family: 'douyuFont';
-      font-size: 20px;
-      letter-spacing: 2px;
+      background: var(--image-modal-top) no-repeat center;
+      background-size: 100% 100%;
+      position: absolute;
+      z-index: 1;
+
+      .main-title {
+        height: 66px;
+        font-family: 'douyuFont';
+        font-size: 20px;
+        letter-spacing: 2px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+
+      .top-nav {
+        position: absolute;
+        top: 0;
+        width: 880px;
+        height: 100%;
+        display: flex;
+        justify-content: flex-start;
+      }
+    }
+
+    .main-container {
+      position: absolute;
+      top: 66px;
+      width: 100%;
+      height: calc(100% - 66px);
+      padding: 0px 10px;
+      box-sizing: border-box;
+    }
+
+    .main-status {
       display: flex;
       justify-content: center;
       align-items: center;
+      width: 440px;
+      height: 80px;
+      font-size: 18px;
+      font-family: 'douyuFont';
+      margin: 10px 0px;
+      background: var(--image-modal-status);
+      background-size: 100% 100%;
     }
 
-    .top-nav {
+    .module-dropdown {
+      padding: 10px;
+      background-image: @vent-configurable-dropdown;
+      border-bottom: 2px solid @vent-configurable-home-light-border;
+      color: @vent-font-color;
       position: absolute;
-      top: 0;
-      width: 880px;
-      height: 100%;
-      display: flex;
-      justify-content: flex-start;
+      top: 70px;
+      right: 460px;
     }
-  }
 
-  .main-container {
-    position: absolute;
-    top: 66px;
-    width: 100%;
-    height: calc(100% - 66px);
-    padding: 0px 10px;
-    box-sizing: border-box;
-  }
+    .modal-box {
+      width: calc(100% - 20px);
+      height: calc(100% - 20px);
+      position: relative;
+      pointer-events: auto;
+      margin: 10px;
+    }
 
-  .main-status {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    width: 440px;
-    height: 80px;
-    font-size: 18px;
-    font-family: 'douyuFont';
-    margin: 10px 0px;
-    background: var(--image-modal-status);
-    background-size: 100% 100%;
+    .module-dropdown-original {
+      padding: 10px;
+      background-image: @vent-configurable-dropdown;
+      border-bottom: 2px solid @vent-configurable-home-light-border;
+      color: @vent-font-color;
+      position: absolute;
+      top: 70px;
+      right: 460px;
+    }
+
+    .module-trigger-button {
+      color: @vent-font-color;
+      background-image: @vent-configurable-dropdown;
+      border: none;
+      border-bottom: 2px solid @vent-configurable-home-light-border;
+    }
+
+    .nav-cards {
+      position: absolute;
+      left: 50%;
+      top: 0px;
+      transform: translate(-50%, 0);
+      width: 691px;
+      height: 58px;
+      background: var(--image-modal-center) no-repeat;
+      background-size: 100% 100%;
+    }
   }
 
-  .module-dropdown {
-    padding: 10px;
-    background-image: @vent-configurable-dropdown;
-    border-bottom: 2px solid @vent-configurable-home-light-border;
-    color: @vent-font-color;
-    position: absolute;
-    top: 70px;
-    right: 460px;
+  :deep(.loading-box) {
+    position: unset;
   }
 
   .modal-box {
-    width: calc(100% - 20px);
-    height: calc(100% - 20px);
+    width: 100%;
+    height: 100%;
     position: relative;
     pointer-events: auto;
-    margin: 10px;
   }
 
-  .module-dropdown-original {
-    padding: 10px;
-    background-image: @vent-configurable-dropdown;
-    border-bottom: 2px solid @vent-configurable-home-light-border;
-    color: @vent-font-color;
+  // 试验区模型区域样式
+  .syq-modal {
     position: absolute;
-    top: 70px;
-    right: 460px;
-  }
-
-  .module-trigger-button {
-    color: @vent-font-color;
-    background-image: @vent-configurable-dropdown;
-    border: none;
-    border-bottom: 2px solid @vent-configurable-home-light-border;
+    top: 135px;
+    right: 20px;
+    width: calc(100% - 510px);
+    height: 515px;
+    background: url('@/assets/images/gasInjection/syq/modal-bg.png') no-repeat;
+    background-size: 100% 100%;
   }
 
-  .nav-cards {
+  .vent-modal {
     position: absolute;
+    top: 70px;
     left: 50%;
-    top: 0px;
-    transform: translate(-50%, 0);
-    width: 691px;
-    height: 58px;
-    background: var(--image-modal-center) no-repeat;
+    transform: translate(-50%, 0px);
+    width: calc(100% - 920px);
+    height: 500px;
+    background: url(/src/assets/images/gasInjection/syq/modal-bg.png) no-repeat;
     background-size: 100% 100%;
+    pointer-events: auto;
+    // padding: 15px;
+    box-sizing: border-box;
+    overflow: hidden;
   }
-}
-
-:deep(.loading-box) {
-  position: unset;
-}
-
-.modal-box {
-  width: 100%;
-  height: 100%;
-  position: relative;
-  pointer-events: auto;
-}
-
-// 试验区模型区域样式
-.syq-modal {
-  position: absolute;
-  top: 135px;
-  right: 20px;
-  width: calc(100% - 510px);
-  height: 515px;
-  background: url('@/assets/images/gasInjection/syq/modal-bg.png') no-repeat;
-  background-size: 100% 100%;
-}
-
-.vent-modal {
-  position: absolute;
-  top: 70px;
-  left: 50%;
-  transform: translate(-50%, 0px);
-  width: calc(100% - 920px);
-  height: 500px;
-  background: url(/src/assets/images/gasInjection/syq/modal-bg.png) no-repeat;
-  background-size: 100% 100%;
-  pointer-events: auto;
-  // padding: 15px;
-  box-sizing: border-box;
-  overflow: hidden;
-}
 
-.vent-modal-1 {
-  position: absolute;
-  top: 70px;
-  left: 460px;
-  width: calc(100% - 470px);
-  height: 500px;
-  background: url(/src/assets/images/gasInjection/syq/modal-bg.png) no-repeat;
-  background-size: 100% 100%;
-  pointer-events: auto;
-  padding: 10px 15px;
-  box-sizing: border-box;
-  overflow: hidden;
-}
+  .vent-modal-1 {
+    position: absolute;
+    top: 70px;
+    left: 460px;
+    width: calc(100% - 470px);
+    height: 500px;
+    background: url(/src/assets/images/gasInjection/syq/modal-bg.png) no-repeat;
+    background-size: 100% 100%;
+    pointer-events: auto;
+    padding: 10px 15px;
+    box-sizing: border-box;
+    overflow: hidden;
+  }
 
-.modal-3d {
-  width: 100%;
-}
+  .modal-3d {
+    width: 100%;
+  }
 </style>

+ 651 - 0
src/views/vent/home/configurable/threejs/gasInjection.ts

@@ -0,0 +1,651 @@
+import * as THREE from 'three';
+import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
+import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
+import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
+import { PathPointList, PathGeometry } from 'three.path';
+
+export function modalAnimate(modal) {
+  modal.renderer.toneMappingExposure = 1.2;
+
+  console.log('初始化模型', modal);
+  modal.isRender = true;
+
+  // 【关键步骤】设置旋转速度
+  // 默认是 1.0,设置为 0.5 会让移动幅度减半(更慢/更精细)
+  modal.orbitControls.rotateSpeed = 0.5;
+
+  // 可选:如果你也想减慢缩放和平移的速度
+  modal.orbitControls.zoomSpeed = 0.5; // 缩放速度
+  modal.orbitControls.panSpeed = 0.5; // 平移速度
+
+  // 启用阻尼效果(可选,让运动更有惯性,感觉更平滑)
+  modal.orbitControls.enableDamping = true;
+  modal.orbitControls.dampingFactor = 0.9;
+
+  const bounds = {
+    min: new THREE.Vector3(-25, -2, -35),
+    max: new THREE.Vector3(10, 10, 30),
+  };
+
+  // 初始化多种颜色气泡
+  const allBubbles = initGasAnimate(modal);
+
+  const zhuqiModal = modal.scene.getObjectByName('zhuqi').getObjectByName('ZhuQiChangJing_1');
+  const touming3 = zhuqiModal.getObjectByName('touming3');
+  touming3.parent.remove(touming3);
+
+  const time = Date.now() * 0.001;
+  const clock = new THREE.Clock();
+
+  const bloomObj = addZhuqiAnimate(zhuqiModal, modal);
+  const arrowObj = addArrow(zhuqiModal);
+  const unitObj = addUnit(zhuqiModal);
+
+  // 帧刷新
+  modal.startAnimation = () => {
+    /**气泡运动 - 遍历所有颜色组*/
+    allBubbles.forEach((bubbleGroup) => {
+      const { bubblesData, instancedMesh, bubbleCount, dummy } = bubbleGroup;
+
+      for (let i = 0; i < bubbleCount; i++) {
+        const data = bubblesData[i];
+
+        // 1. 更新位置
+        data.position.add(data.velocity);
+
+        // 2. 添加正弦波动
+        data.position.y += Math.sin(time + i) * 0.02;
+
+        // 3. 边界检测与反弹
+        if (data.position.x < bounds.min.x || data.position.x > bounds.max.x) {
+          data.velocity.x *= -1;
+          data.position.x = THREE.MathUtils.clamp(data.position.x, bounds.min.x, bounds.max.x);
+        }
+        if (data.position.y < bounds.min.y || data.position.y > bounds.max.y) {
+          data.velocity.y *= -1;
+          data.position.y = THREE.MathUtils.clamp(data.position.y, bounds.min.y, bounds.max.y);
+        }
+        if (data.position.z < bounds.min.z || data.position.z > bounds.max.z) {
+          data.velocity.z *= -1;
+          data.position.z = THREE.MathUtils.clamp(data.position.z, bounds.min.z, bounds.max.z);
+        }
+
+        // 4. 更新实例矩阵
+        dummy.position.copy(data.position);
+        dummy.scale.setScalar(data.scale);
+        dummy.rotation.y = time * 0.5;
+        dummy.rotation.z = time * 0.2;
+        dummy.updateMatrix();
+        instancedMesh.setMatrixAt(i, dummy.matrix);
+      }
+      instancedMesh.instanceMatrix.needsUpdate = true;
+    });
+
+    /**自发光*/
+    if (bloomObj) {
+      // const { zhuqiPips, bloomComposer, finalComposer, materials, darkMaterial, shader, bloomLayer } = bloomObj;
+      // modal.scene.traverse((item) => {
+      //   darkenNonBloomed(item, bloomLayer, materials, darkMaterial);
+      // });
+      // bloomComposer.render();
+      // modal.scene.traverse((item) => {
+      //   restoreMaterial(item, materials);
+      // });
+      // finalComposer.render();
+    }
+
+    /**箭头流动效果*/
+    if (arrowObj) {
+      const { texture } = arrowObj;
+      if (texture) {
+        texture.offset.x -= 0.1776;
+      }
+    }
+    if (unitObj) {
+      const { arrowTexture } = unitObj;
+      arrowTexture.offset.x -= 0.0576;
+    }
+  };
+}
+
+function initGasAnimate(modal, colorConfigs: any[] = []) {
+  // 默认配置:如果没有传入颜色配置,使用默认的红蓝两种
+  if (colorConfigs.length === 0) {
+    colorConfigs = [
+      {
+        name: 'gas',
+        color: 0xffa2a244,
+        emissive: 0xff4400,
+        bubbleCount: 200,
+        hslHue: 0.4, // 红色
+      },
+      {
+        name: 'n2',
+        color: 0x4fedf788,
+        emissive: 0x0044aa,
+        bubbleCount: 500,
+        hslHue: 0.5, // 蓝色
+      },
+    ];
+  }
+
+  // 1. 定义容器边界 (矩形)
+  const bounds = {
+    min: new THREE.Vector3(-25, -2, -35),
+    max: new THREE.Vector3(10, 10, 30),
+  };
+
+  const allBubbles = [] as {
+    bubblesData: { position: THREE.Vector3; velocity: THREE.Vector3; scale: number }[];
+    instancedMesh: THREE.InstancedMesh;
+    bubbleCount: number;
+    dummy: THREE.Object3D;
+  }[];
+
+  // 2. 为每种颜色配置创建气泡
+  colorConfigs.forEach((config) => {
+    const { bubbleCount, color, emissive, hslHue } = config;
+    const geometry = new THREE.SphereGeometry(1, 16, 16);
+
+    // 创建材质
+    const material = new THREE.MeshPhysicalMaterial({
+      color: color,
+      emissive: emissive,
+      emissiveIntensity: 0.4,
+      transparent: true,
+      opacity: 0.1,
+      depthWrite: false,
+      roughness: 0.0,
+      metalness: 0.1,
+      clearcoat: 1.0,
+      clearcoatRoughness: 0.1,
+      transmission: 0.7,
+      thickness: 0.5,
+      ior: 1.0004,
+      // blending: THREE.AdditiveBlending,
+      side: THREE.DoubleSide,
+    });
+
+    // 创建实例化网格
+    const instancedMesh = new THREE.InstancedMesh(geometry, material, bubbleCount);
+    modal.scene.add(instancedMesh);
+
+    // 存储气泡运动数据
+    const dummy = new THREE.Object3D();
+    const bubblesData = [] as { position: THREE.Vector3; velocity: THREE.Vector3; scale: number }[];
+
+    for (let i = 0; i < bubbleCount; i++) {
+      const x = THREE.MathUtils.randFloat(bounds.min.x, bounds.max.x);
+      const y = THREE.MathUtils.randFloat(bounds.min.y, bounds.max.y);
+      const z = THREE.MathUtils.randFloat(bounds.min.z, bounds.max.z);
+      const scale = THREE.MathUtils.randFloat(0.2, 0.8);
+      const velocity = new THREE.Vector3(
+        THREE.MathUtils.randFloat(-0.05, 0.05),
+        THREE.MathUtils.randFloat(-0.05, 0.05),
+        THREE.MathUtils.randFloat(-0.05, 0.05)
+      );
+
+      bubblesData.push({ position: new THREE.Vector3(x, y, z), velocity, scale });
+
+      dummy.position.copy(bubblesData[i].position);
+      dummy.scale.setScalar(bubblesData[i].scale);
+      dummy.updateMatrix();
+      instancedMesh.setMatrixAt(i, dummy.matrix);
+    }
+
+    // 设置实例颜色
+    const colorObj = new THREE.Color();
+    for (let i = 0; i < bubbleCount; i++) {
+      colorObj.setHSL(hslHue, 0.5, 0.8 + Math.random() * 0.2);
+      instancedMesh.setColorAt(i, colorObj);
+    }
+    instancedMesh.instanceColor.needsUpdate = true;
+
+    allBubbles.push({ bubblesData, instancedMesh, bubbleCount, dummy });
+  });
+
+  return allBubbles;
+}
+
+function addZhuqiAnimate(zhuqiModal, modal) {
+  return null;
+  const renderer = modal.renderer;
+
+  const renderScene = new RenderPass(modal.scene, modal.camera);
+  const BLOOM_SCENE = 1;
+
+  const bloomLayer = new THREE.Layers();
+  bloomLayer.set(BLOOM_SCENE);
+
+  const params = {
+    threshold: 0,
+    strength: 0,
+    radius: 0.001,
+    exposure: 0,
+  };
+
+  const materials = {};
+
+  const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' });
+
+  const bloomPass = new UnrealBloomPass(new THREE.Vector2(renderer.domElement.innerWidth, renderer.domElement.innerHeight), 1.5, 0.4, 0.85);
+  bloomPass.threshold = params.threshold;
+  bloomPass.strength = params.strength;
+  bloomPass.radius = params.radius;
+
+  const bloomRenderTarget = new THREE.WebGLRenderTarget(renderer.domElement.innerWidth, renderer.domElement.innerHeight, {
+    type: THREE.HalfFloatType,
+  });
+  const bloomComposer = new EffectComposer(renderer, bloomRenderTarget);
+  bloomComposer.renderToScreen = false;
+  bloomComposer.addPass(renderScene);
+  bloomComposer.addPass(bloomPass);
+  const shader = new THREE.ShaderMaterial({
+    uniforms: {
+      baseTexture: { value: null },
+      bloomTexture: { value: bloomComposer.renderTarget2.texture },
+      bloomStrength: { value: params.strength },
+    },
+    vertexShader: `varying vec2 vUv;
+
+			void main() {
+
+				vUv = uv;
+
+				gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+
+			}`,
+    fragmentShader: `uniform sampler2D baseTexture;
+			uniform sampler2D bloomTexture;
+			uniform float bloomStrength;
+
+			varying vec2 vUv;
+
+			void main() {
+
+				gl_FragColor = ( texture2D( baseTexture, vUv ) + texture2D( bloomTexture, vUv ) * bloomStrength );
+
+			}`,
+    defines: {},
+  });
+
+  const mixPass = new ShaderPass(shader, 'baseTexture');
+  mixPass.needsSwap = true;
+
+  const outputPass = new OutputPass();
+
+  const finalRenderTarget = new THREE.WebGLRenderTarget(modal.canvasContainer.clientWidth, modal.canvasContainer.clientHeight, {
+    type: THREE.HalfFloatType,
+    samples: 4,
+  });
+  const finalComposer = new EffectComposer(renderer, finalRenderTarget);
+  finalComposer.addPass(renderScene);
+  finalComposer.addPass(mixPass);
+  finalComposer.addPass(outputPass);
+
+  // 注气动画
+  const changjingzhuqiguan = zhuqiModal.getObjectByName('changjingzhuqiguan');
+  if (changjingzhuqiguan) {
+    const zhuqiPips = changjingzhuqiguan.children.filter((item) => {
+      return item.name.includes('changjingwire');
+    });
+    zhuqiPips.forEach((item) => {
+      // modal.scene.add(item);
+      const material = new THREE.MeshStandardMaterial({ color: '#f00', roughness: 1, metalness: 1 });
+      item.material = material;
+      item.layers.enable(BLOOM_SCENE);
+    });
+
+    return { zhuqiPips, bloomComposer, finalComposer, materials, darkMaterial, shader, bloomLayer };
+  }
+  return null;
+}
+
+function addArrow1(zhuqiModal) {
+  const changjingzhuqiguan = zhuqiModal.getObjectByName('changjing5');
+  if (changjingzhuqiguan) {
+    const zhuqiPips = changjingzhuqiguan.children.filter((item) => {
+      return item.name.includes('changjingcurve10');
+    });
+
+    const flowUniforms = {
+      uTime: { value: 0 },
+      uColor: { value: new THREE.Color(0x00aaff) }, // 流体颜色
+      uSpeed: { value: 0.5 }, // 流动速度
+      uBaseColor: { value: new THREE.Color(0x222222) }, // 管道底色
+    };
+
+    const flowMaterial = new THREE.ShaderMaterial({
+      uniforms: flowUniforms,
+      vertexShader: `
+        varying vec2 vUv;
+        void main() {
+            vUv = uv;
+            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+        }
+    `,
+      fragmentShader: `
+        uniform float uTime;
+        uniform vec3 uColor;
+        uniform float uSpeed;
+        uniform vec3 uBaseColor;
+        varying vec2 vUv;
+
+        void main() {
+            // 核心:沿 V 轴 (长度方向) 移动纹理
+            // fract 实现无限循环
+            float flow = fract(vUv.y + uTime * uSpeed);
+            
+            // 简单的条纹效果
+            float stripe = step(0.8, fract(flow * 5.0)); 
+            
+            // 混合颜色:底色 + 流动的高亮色
+            vec3 finalColor = mix(uBaseColor, uColor, stripe * 0.8);
+            
+            // 增加一点菲涅尔效果让它看起来更立体 (可选)
+            gl_FragColor = vec4(finalColor, 1.0);
+        }
+    `,
+      side: THREE.DoubleSide,
+    });
+
+    const material1 = new THREE.MeshStandardMaterial({
+      color: '#f00',
+      roughness: 1,
+      metalness: 1,
+    });
+    debugger;
+    zhuqiPips.forEach((item) => {
+      // const oldGeometry = item.geometry.clone();
+      // // const geometry = new THREE.TubeGeometry(oldGeometry.parameters.path, oldGeometry.parameters.tubularSegments, 0.05, 8, false);
+      // const newMesh = new THREE.Mesh(oldGeometry, flowMaterial.clone());
+      // item.parent.add(newMesh);
+      // // item.parent.remove(item);
+      // item.visible = false;
+      // // material.needsUpdate = true;
+      // // item.material = material;
+    });
+    return { zhuqiPips, flowMaterial };
+  }
+  return null;
+}
+
+function addArrow(zhuqiModal, curveNum = 8) {
+  const changjing5 = zhuqiModal.getObjectByName('changjing5');
+  if (changjing5) {
+    const pCube53 = changjing5.getObjectByName('pCube53');
+    pCube53.material.color.set('#888');
+    // 盒子
+    const box = new THREE.Box3();
+    box.setFromObject(pCube53);
+    const min = box.min;
+    const max = box.max;
+    const height = -300;
+    // 获取包围盒的四个顶点
+    const vertices = [
+      new THREE.Vector3(min.x + 200, min.y + 500, min.z), // 点 4
+      new THREE.Vector3(max.x, min.y + 500, min.z), //点3
+      new THREE.Vector3(min.x + 200, max.y, min.z), // 起点1
+      new THREE.Vector3(max.x, max.y, min.z), // 点2
+    ];
+
+    // 绘制钻孔曲线
+    // 根据份数计算均线位置, max.x - min.x 为总长, 端点坐标
+    // 纵长
+    const num1 = 9; // 钻孔个数+1
+    const gap = (max.x - (min.x + 200)) / num1;
+    const curvePoints: THREE.Vector3[] = [];
+    for (let i = 0; i < num1; i++) {
+      if (i !== 0) {
+        const x = min.x + 200 + gap * i;
+        const y = min.y + 500;
+        const z = min.z + height;
+        const position = new THREE.Vector3(x, y, z);
+        curvePoints.push(position);
+
+        // const ball = new THREE.Mesh(new THREE.SphereGeometry(50, 32, 32), new THREE.MeshStandardMaterial({ color: '#f00' }));
+        // ball.position.set(position.x, position.y, position.z);
+        // pCube53.add(ball);
+      }
+    }
+
+    // 绘制终点集合
+    // (max.y - min.y + 300)
+    const startP = min.y + 300 + (max.y - (min.y + 300)) / 2; // 起点
+    const len = max.y - startP;
+    const curvePoints1: THREE.Vector3[] = [];
+    for (let i = 0; i < curveNum; i++) {
+      const x = curvePoints[curvePoints.length - i - 1].x;
+      const y = startP + (len / curveNum) * i;
+      const z = min.z + height;
+      const position = new THREE.Vector3(x, y, z);
+      curvePoints1.push(position);
+
+      // const ball = new THREE.Mesh(new THREE.SphereGeometry(50, 32, 32), new THREE.MeshStandardMaterial({ color: '#ff0' }));
+      // ball.position.set(position.x, position.y, position.z);
+      // pCube53.add(ball);
+    }
+
+    // 绘制起点集合
+    const curvePoints0: THREE.Vector3[] = [];
+    for (let i = 0; i < curvePoints.length; i++) {
+      const position = new THREE.Vector3(vertices[2].x + 50 * i, vertices[2].y - 50, min.z);
+      curvePoints0.unshift(position);
+      // const ball = new THREE.Mesh(new THREE.SphereGeometry(50, 32, 32), new THREE.MeshStandardMaterial({ color: '#0ff' }));
+      // ball.position.copy(position);
+      // pCube53.add(ball);
+    }
+
+    // 绘制控制点
+    const curvePoints2: THREE.Vector3[] = [];
+    for (let i = 0; i < curvePoints.length; i++) {
+      const y = curvePoints1[i].y + (vertices[2].y - curvePoints1[i].y) / 2;
+      const x = curvePoints1[i].x - gap / 2;
+      const position = new THREE.Vector3(x, y, min.z + height / 2);
+      curvePoints2.push(position);
+      // const ball = new THREE.Mesh(new THREE.SphereGeometry(50, 32, 32), new THREE.MeshStandardMaterial({ color: '#00f' }));
+      // ball.position.copy(position);
+      // pCube53.add(ball);
+    }
+
+    const textureLoader = new THREE.TextureLoader();
+    const texture = textureLoader.load(`/public/texture/2-1.png`);
+
+    // 设置阵列模式 RepeatWrapping
+    texture.wrapS = THREE.RepeatWrapping;
+    texture.wrapT = THREE.RepeatWrapping;
+    // 设置x方向的重复数(沿着管道路径方向)
+    // 设置y方向的重复数(环绕管道方向)
+    texture.repeat.x = 50;
+    texture.repeat.y = 6;
+    // 设置管道纹理偏移数,便于对中
+    texture.offset.y = 100;
+    const tubeMaterial = new THREE.MeshPhongMaterial({
+      map: texture,
+      color: '#fff',
+      transparent: true,
+      opacity: 1.0,
+    });
+
+    // 根据起点curvePoints0、控制点curvePoints2、终点curvePoints1集合 绘制多条管道
+    for (let i = 0; i < curvePoints0.length; i++) {
+      const contrlP2 = new THREE.Vector3(curvePoints1[i].x, curvePoints1[i].y - 500 * (i + 1), curvePoints1[i].z);
+      const points = [curvePoints0[i], curvePoints2[i], curvePoints1[i], contrlP2];
+      const vec3List = new THREE.CubicBezierCurve3(...points).getPoints();
+      vec3List.push(curvePoints[curvePoints0.length - i - 1]);
+      const geometry = new THREE.TubeGeometry(new THREE.CatmullRomCurve3(vec3List), 100, 10, 8, false);
+      const material = new THREE.MeshStandardMaterial({
+        color: '#4F4F4F',
+        roughness: 0.31,
+        metalness: 0.74,
+      });
+      const mesh = new THREE.Mesh(geometry, material);
+      pCube53.add(mesh);
+
+      const geometry1 = new THREE.TubeGeometry(new THREE.CatmullRomCurve3(vec3List), 100, 40, 8, false);
+      const mesh1 = new THREE.Mesh(geometry1, tubeMaterial);
+      pCube53.add(mesh1);
+      mesh1.renderOrder = 1;
+    }
+    return { texture };
+  }
+  return null;
+}
+
+function addUnit(zhuqiModal, unitNum = 8) {
+  const changjingzhuqiguan = zhuqiModal.getObjectByName('changjingzhuqiguan');
+  if (changjingzhuqiguan) {
+    changjingzhuqiguan.parent.remove(changjingzhuqiguan);
+  }
+  const choucaiguandao = zhuqiModal.getObjectByName('choucaiguandao');
+  if (choucaiguandao) {
+    choucaiguandao.parent.remove(choucaiguandao);
+  }
+
+  const changjing5 = zhuqiModal.getObjectByName('changjing5');
+  if (changjing5) {
+    const pCube53 = changjing5.getObjectByName('pCube53');
+    // 盒子
+    const box = new THREE.Box3();
+    box.setFromObject(pCube53);
+    const min = box.min;
+    const max = box.max;
+    const height = 300;
+    // 获取包围盒的四个顶点
+    const vertices = [
+      new THREE.Vector3(min.x, min.y + 400, min.z + height), // 点 4
+      new THREE.Vector3(max.x, min.y + 400, min.z + height), //点3
+      new THREE.Vector3(min.x, max.y - 700, min.z + height), // 起点1
+      new THREE.Vector3(max.x, max.y - 700, min.z + height), // 点2
+    ];
+
+    // const num = 4;
+    // // 绘制 mun 个小球
+    // for (let i = 0; i < num; i++) {
+    //   const ball = new THREE.Mesh(new THREE.SphereGeometry(100, 32, 32), new THREE.MeshStandardMaterial({ color: '#f00' }));
+    //   ball.position.set(vertices[i].x, vertices[i].y, vertices[i].z);
+    //   pCube53.add(ball);
+    // }
+
+    const textureLoader = new THREE.TextureLoader();
+    const texture = textureLoader.load(`/public/texture/3-2.png`);
+
+    // 设置阵列模式 RepeatWrapping
+    texture.wrapS = THREE.RepeatWrapping;
+    texture.wrapT = THREE.RepeatWrapping;
+    // 设置x方向的重复数(沿着管道路径方向)
+    // 设置y方向的重复数(环绕管道方向)
+    texture.repeat.x = 0.4;
+    texture.repeat.y = 1;
+    // // 设置管道纹理偏移数,便于对中
+    texture.offset.y = 1;
+    const tubeMaterial = new THREE.MeshPhongMaterial({
+      map: texture,
+      // color: '#FF3B40',
+      color: '#FFC4C4',
+      transparent: true,
+      opacity: 0.5,
+      side: THREE.DoubleSide,
+    });
+
+    // 横长
+    const len = max.y - 700 - (min.y + 400);
+    const gap = len / unitNum;
+    const curvePoints: THREE.Vector3[] = [];
+    // 绘制区域面
+    let lastP = new THREE.Vector3(vertices[0].x, vertices[0].y, vertices[0].z);
+    for (let i = 0; i < unitNum; i++) {
+      let p1, p2, p3, p4;
+      if (i == 0) {
+        const x = vertices[0].x;
+        const y = vertices[0].y + gap;
+        const z = vertices[0].z;
+        p1 = lastP.clone();
+        p2 = new THREE.Vector3(x, y, z);
+        p3 = new THREE.Vector3(max.x, y, z);
+        p4 = new THREE.Vector3(max.x, vertices[0].y, vertices[0].z);
+        lastP = p2.clone();
+      } else {
+        p1 = lastP.clone();
+        p2 = new THREE.Vector3(p1.x, p1.y + gap, p1.z);
+        p3 = new THREE.Vector3(max.x, p2.y, p2.z);
+        p4 = new THREE.Vector3(max.x, p1.y, p1.z);
+        lastP = p2.clone();
+      }
+
+      // 绘制面
+      if (p1 && p2 && p3 && p4) {
+        const geometry = new THREE.BufferGeometry();
+        geometry.setFromPoints([p1, p2, p3, p3, p4, p1]);
+        const material = new THREE.MeshPhongMaterial({
+          color: '#f00',
+        });
+        const mesh = new THREE.Mesh(geometry, tubeMaterial);
+        pCube53.add(mesh);
+
+        // p1 p2 p3 p4
+        // 在该区域内绘制管道, p2->p3走向的管道
+        const num = 5; // 管道数量-1
+        let startGap = getMinGapThreshold(gap, num - 2);
+        startGap = startGap + startGap / 3;
+        const unitGap = (gap - startGap * 2) / (num - 2);
+        const gapStartP = new THREE.Vector3(p1.x, p1.y + startGap, p1.z);
+        for (let i = 0; i < num - 1; i++) {
+          const startP = new THREE.Vector3(gapStartP.x, gapStartP.y + unitGap * i, gapStartP.z);
+          const endP = new THREE.Vector3(max.x, startP.y, startP.z);
+          const geometry = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([startP, endP]), 100, 6, 8, false);
+          const material = new THREE.MeshStandardMaterial({
+            color: '#6F6F6F',
+            roughness: 0.31,
+            metalness: 0.74,
+          });
+          const mesh = new THREE.Mesh(geometry, material);
+          pCube53.add(mesh);
+        }
+
+        const y = p1.y + (p2.y - p1.y) / 2;
+        const startP = new THREE.Vector3(p1.x, y, p1.z - 20);
+        const endP = new THREE.Vector3(max.x, y, p1.z - 20);
+        const cuver = new THREE.CatmullRomCurve3([endP, startP]);
+        const points = cuver.getPoints(64);
+        // const geometry1 = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([endP, startP]), 64, 50, 4, false);
+        // const mesh1 = new THREE.Mesh(geometry1, tubeMaterial);
+        // pCube53.add(mesh1);
+
+        const pathPointList = new PathPointList();
+        const up = new THREE.Vector3(0, 0, -1);
+        pathPointList.set(points, 0, 0, up, false);
+        const geometry1 = new PathGeometry(points.length, false);
+        geometry1.update(pathPointList, {
+          width: 200,
+          arrow: false,
+        });
+        const mesh1 = new THREE.Mesh(geometry1, tubeMaterial);
+        mesh1.renderOrder = 99;
+        pCube53.add(mesh1);
+      }
+    }
+    return { arrowTexture: texture };
+  }
+  return null;
+}
+
+/**
+ * 计算满足不等式 gap*2 > (len - gap*2)/num 的最小临界值
+ * @param {number} len - 总长度
+ * @param {number} num - 数量/分段数
+ * @returns {number} 返回 gap 必须大于的这个值
+ */
+function getMinGapThreshold(len, num) {
+  if (num <= 0) {
+    throw new Error('num 必须大于 0');
+  }
+  if (len <= 0) {
+    return 0;
+  }
+
+  // 公式推导结果: gap > len / (2 * (num + 1))
+  return len / (2 * (num + 1));
+}