Browse Source

测风装置、局部通风机

hrx 2 years ago
parent
commit
4ad8c4d41f
50 changed files with 4017 additions and 240 deletions
  1. 9 0
      build/vite/plugin/index.ts
  2. 5 0
      package.json
  3. 0 0
      public/js/liveplayer-lib.min.js
  4. 8 0
      public/model/draco/gltf/draco_decoder.js
  5. BIN
      public/model/draco/gltf/draco_decoder.wasm
  6. 3 0
      public/model/draco/gltf/draco_encoder.js
  7. 104 0
      public/model/draco/gltf/draco_wasm_wrapper.js
  8. BIN
      public/model/glft/cf/lmcf.glb
  9. BIN
      public/model/glft/cf/zdcf.glb
  10. BIN
      public/model/glft/fc/ddFc.glb
  11. BIN
      public/model/glft/fc/fc.glb
  12. BIN
      public/model/glft/fc/sdFc.glb
  13. BIN
      public/model/glft/fm/fm.glb
  14. BIN
      public/model/glft/jbfj/jbfj_fc.glb
  15. BIN
      public/model/glft/jbfj/jbfj_fm.glb
  16. BIN
      public/model/glft/jbfj/jbfj_hd.glb
  17. 12 9
      src/App.vue
  18. 2 2
      src/assets/less/modal.less
  19. 164 0
      src/components/Container/src/Adaptive.vue
  20. 57 0
      src/hooks/core/threejs/RafHelper.ts
  21. 19 36
      src/hooks/core/threejs/useThree.ts
  22. 5 1
      src/hooks/setting/useMenuSetting.ts
  23. 0 1
      src/logics/initAppConfig.ts
  24. 0 2
      src/main.ts
  25. 33 12
      src/utils/index.ts
  26. 109 0
      src/utils/threejs/FlyLine1.ts
  27. 1 1
      src/utils/threejs/ResourceTracker.js
  28. 1 1
      src/utils/threejs/main.worker.ts
  29. 90 1
      src/utils/threejs/util.ts
  30. 1 1
      src/views/demo/threejs/damper.vue
  31. 1 1
      src/views/vent/comment/EditRowTable.vue
  32. 2 2
      src/views/vent/deviceManager/windWindowTabel/ventanalyWindow.data.ts
  33. 43 31
      src/views/vent/monitorManager/comment/MonitorTable.vue
  34. 3 4
      src/views/vent/monitorManager/comment/MonitorTable1.vue
  35. 298 0
      src/views/vent/monitorManager/fanLocalMonitor/fanLocal.three.ts
  36. 27 0
      src/views/vent/monitorManager/fanLocalMonitor/fcfanLocal.three.ts
  37. 26 0
      src/views/vent/monitorManager/fanLocalMonitor/fmfanLocal.three.ts
  38. 273 9
      src/views/vent/monitorManager/fanLocalMonitor/index.vue
  39. 1 1
      src/views/vent/monitorManager/gateMonitor/detail.vue
  40. 595 0
      src/views/vent/monitorManager/gateMonitor/gate.threejs.ts
  41. 101 43
      src/views/vent/monitorManager/gateMonitor/index.vue
  42. 280 0
      src/views/vent/monitorManager/windowMonitor/dandaoFc.threejs.ts
  43. 150 29
      src/views/vent/monitorManager/windowMonitor/index.vue
  44. 310 0
      src/views/vent/monitorManager/windowMonitor/shuangdaoFc.threejs.ts
  45. 3 45
      src/views/vent/monitorManager/windowMonitor/window.api.ts
  46. 188 0
      src/views/vent/monitorManager/windowMonitor/window.threejs.ts
  47. 309 8
      src/views/vent/monitorManager/windrectMonitor/index.vue
  48. 302 0
      src/views/vent/monitorManager/windrectMonitor/longmen.threejs.ts
  49. 191 0
      src/views/vent/monitorManager/windrectMonitor/windrect.threejs.ts
  50. 291 0
      src/views/vent/monitorManager/windrectMonitor/zhedie.threejs.ts

+ 9 - 0
build/vite/plugin/index.ts

@@ -17,6 +17,7 @@ import { configSvgIconsPlugin } from './svgSprite';
 import { configHmrPlugin } from './hmr';
 import OptimizationPersist from 'vite-plugin-optimize-persist';
 import PkgConfig from 'vite-plugin-package-config';
+import copy from 'rollup-plugin-copy'
 import glsl from "rollup-plugin-glsl";
 
 export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
@@ -29,6 +30,7 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
     vueJsx(),
     // support name
     vueSetupExtend(),
+
   ];
 
   // vite-plugin-windicss
@@ -61,6 +63,8 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
   //vite-plugin-theme
   vitePlugins.push(configThemePlugin(isBuild));
 
+
+
   // The following plugins only work in the production environment
   if (isBuild) {
     //vite-plugin-imagemin
@@ -86,5 +90,10 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
       // sourceMap: false,
     })
   )
+  vitePlugins.push(copy({
+    targets: [
+      {src: 'node_modules/@liveqing/liveplayer-v3/dist/component/liveplayer-lib.min.js', dest: 'public/js'},
+    ]
+  }) as Plugin)
   return vitePlugins;
 }

+ 5 - 0
package.json

@@ -36,6 +36,7 @@
   "dependencies": {
     "@iconify/iconify": "^2.0.4",
     "@jeecg/online": "1.0.1",
+    "@liveqing/liveplayer-v3": "^3.1.9",
     "@vueuse/core": "^6.6.2",
     "@zxcvbn-ts/core": "^1.0.0-beta.0",
     "ant-design-vue": "^2.2.8",
@@ -51,6 +52,7 @@
     "dom-align": "^1.12.2",
     "echarts": "^5.2.1",
     "enquire.js": "^2.1.6",
+    "esbuild-windows-64": "^0.15.10",
     "gsap": "^3.11.0",
     "intro.js": "^4.2.2",
     "lodash-es": "^4.17.21",
@@ -64,6 +66,7 @@
     "qrcode": "^1.4.4",
     "qrcodejs2": "0.0.2",
     "resize-observer-polyfill": "^1.5.1",
+    "rollup-plugin-copy": "^3.4.0",
     "showdown": "^1.9.1",
     "sortablejs": "^1.14.0",
     "three": "^0.144.0",
@@ -186,6 +189,7 @@
         "@ant-design/colors",
         "@ant-design/icons-vue",
         "@jeecg/online",
+        "@liveqing/liveplayer-v3",
         "@vue/reactivity",
         "@vueuse/core",
         "@vueuse/shared",
@@ -232,6 +236,7 @@
         "echarts/renderers",
         "gsap",
         "intro.js",
+        "lodash",
         "lodash-es",
         "md5",
         "moment",

File diff suppressed because it is too large
+ 0 - 0
public/js/liveplayer-lib.min.js


File diff suppressed because it is too large
+ 8 - 0
public/model/draco/gltf/draco_decoder.js


BIN
public/model/draco/gltf/draco_decoder.wasm


File diff suppressed because it is too large
+ 3 - 0
public/model/draco/gltf/draco_encoder.js


+ 104 - 0
public/model/draco/gltf/draco_wasm_wrapper.js

@@ -0,0 +1,104 @@
+var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(f){var m=0;return function(){return m<f.length?{done:!1,value:f[m++]}:{done:!0}}};$jscomp.arrayIterator=function(f){return{next:$jscomp.arrayIteratorImpl(f)}};$jscomp.makeIterator=function(f){var m="undefined"!=typeof Symbol&&Symbol.iterator&&f[Symbol.iterator];return m?m.call(f):$jscomp.arrayIterator(f)};
+$jscomp.getGlobal=function(f){return"undefined"!=typeof window&&window===f?f:"undefined"!=typeof global&&null!=global?global:f};$jscomp.global=$jscomp.getGlobal(this);$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(f,m,v){f!=Array.prototype&&f!=Object.prototype&&(f[m]=v.value)};
+$jscomp.polyfill=function(f,m,v,t){if(m){v=$jscomp.global;f=f.split(".");for(t=0;t<f.length-1;t++){var h=f[t];h in v||(v[h]={});v=v[h]}f=f[f.length-1];t=v[f];m=m(t);m!=t&&null!=m&&$jscomp.defineProperty(v,f,{configurable:!0,writable:!0,value:m})}};$jscomp.FORCE_POLYFILL_PROMISE=!1;
+$jscomp.polyfill("Promise",function(f){function m(){this.batch_=null}function v(e){return e instanceof h?e:new h(function(l,f){l(e)})}if(f&&!$jscomp.FORCE_POLYFILL_PROMISE)return f;m.prototype.asyncExecute=function(e){if(null==this.batch_){this.batch_=[];var l=this;this.asyncExecuteFunction(function(){l.executeBatch_()})}this.batch_.push(e)};var t=$jscomp.global.setTimeout;m.prototype.asyncExecuteFunction=function(e){t(e,0)};m.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var e=
+this.batch_;this.batch_=[];for(var l=0;l<e.length;++l){var f=e[l];e[l]=null;try{f()}catch(z){this.asyncThrow_(z)}}}this.batch_=null};m.prototype.asyncThrow_=function(e){this.asyncExecuteFunction(function(){throw e;})};var h=function(e){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];var l=this.createResolveAndReject_();try{e(l.resolve,l.reject)}catch(S){l.reject(S)}};h.prototype.createResolveAndReject_=function(){function e(e){return function(h){f||(f=!0,e.call(l,h))}}var l=this,f=!1;
+return{resolve:e(this.resolveTo_),reject:e(this.reject_)}};h.prototype.resolveTo_=function(e){if(e===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(e instanceof h)this.settleSameAsPromise_(e);else{a:switch(typeof e){case "object":var l=null!=e;break a;case "function":l=!0;break a;default:l=!1}l?this.resolveToNonPromiseObj_(e):this.fulfill_(e)}};h.prototype.resolveToNonPromiseObj_=function(e){var l=void 0;try{l=e.then}catch(S){this.reject_(S);return}"function"==typeof l?
+this.settleSameAsThenable_(l,e):this.fulfill_(e)};h.prototype.reject_=function(e){this.settle_(2,e)};h.prototype.fulfill_=function(e){this.settle_(1,e)};h.prototype.settle_=function(e,l){if(0!=this.state_)throw Error("Cannot settle("+e+", "+l+"): Promise already settled in state"+this.state_);this.state_=e;this.result_=l;this.executeOnSettledCallbacks_()};h.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var e=0;e<this.onSettledCallbacks_.length;++e)X.asyncExecute(this.onSettledCallbacks_[e]);
+this.onSettledCallbacks_=null}};var X=new m;h.prototype.settleSameAsPromise_=function(e){var l=this.createResolveAndReject_();e.callWhenSettled_(l.resolve,l.reject)};h.prototype.settleSameAsThenable_=function(e,l){var f=this.createResolveAndReject_();try{e.call(l,f.resolve,f.reject)}catch(z){f.reject(z)}};h.prototype.then=function(e,f){function l(e,f){return"function"==typeof e?function(f){try{m(e(f))}catch(p){v(p)}}:f}var m,v,t=new h(function(e,f){m=e;v=f});this.callWhenSettled_(l(e,m),l(f,v));return t};
+h.prototype.catch=function(e){return this.then(void 0,e)};h.prototype.callWhenSettled_=function(e,f){function l(){switch(h.state_){case 1:e(h.result_);break;case 2:f(h.result_);break;default:throw Error("Unexpected state: "+h.state_);}}var h=this;null==this.onSettledCallbacks_?X.asyncExecute(l):this.onSettledCallbacks_.push(l)};h.resolve=v;h.reject=function(e){return new h(function(f,h){h(e)})};h.race=function(e){return new h(function(f,h){for(var l=$jscomp.makeIterator(e),m=l.next();!m.done;m=l.next())v(m.value).callWhenSettled_(f,
+h)})};h.all=function(e){var f=$jscomp.makeIterator(e),m=f.next();return m.done?v([]):new h(function(e,h){function l(f){return function(h){t[f]=h;z--;0==z&&e(t)}}var t=[],z=0;do t.push(void 0),z++,v(m.value).callWhenSettled_(l(t.length-1),h),m=f.next();while(!m.done)})};return h},"es6","es3");
+var DracoDecoderModule=function(){var f="undefined"!==typeof document&&document.currentScript?document.currentScript.src:void 0;"undefined"!==typeof __filename&&(f=f||__filename);return function(m){function v(k){return a.locateFile?a.locateFile(k,M):M+k}function t(a,c){a||z("Assertion failed: "+c)}function h(a,c,b){var d=c+b;for(b=c;a[b]&&!(b>=d);)++b;if(16<b-c&&a.subarray&&xa)return xa.decode(a.subarray(c,b));for(d="";c<b;){var k=a[c++];if(k&128){var e=a[c++]&63;if(192==(k&224))d+=String.fromCharCode((k&
+31)<<6|e);else{var f=a[c++]&63;k=224==(k&240)?(k&15)<<12|e<<6|f:(k&7)<<18|e<<12|f<<6|a[c++]&63;65536>k?d+=String.fromCharCode(k):(k-=65536,d+=String.fromCharCode(55296|k>>10,56320|k&1023))}}else d+=String.fromCharCode(k)}return d}function X(a,c){return a?h(ca,a,c):""}function e(a,c){0<a%c&&(a+=c-a%c);return a}function l(k){ka=k;a.HEAP8=T=new Int8Array(k);a.HEAP16=new Int16Array(k);a.HEAP32=P=new Int32Array(k);a.HEAPU8=ca=new Uint8Array(k);a.HEAPU16=new Uint16Array(k);a.HEAPU32=new Uint32Array(k);
+a.HEAPF32=new Float32Array(k);a.HEAPF64=new Float64Array(k)}function S(k){for(;0<k.length;){var c=k.shift();if("function"==typeof c)c();else{var b=c.func;"number"===typeof b?void 0===c.arg?a.dynCall_v(b):a.dynCall_vi(b,c.arg):b(void 0===c.arg?null:c.arg)}}}function z(k){if(a.onAbort)a.onAbort(k);k+="";ya(k);Y(k);za=!0;throw new WebAssembly.RuntimeError("abort("+k+"). Build with -s ASSERTIONS=1 for more info.");}function va(a){return String.prototype.startsWith?a.startsWith("data:application/octet-stream;base64,"):
+0===a.indexOf("data:application/octet-stream;base64,")}function wa(){try{if(da)return new Uint8Array(da);if(la)return la(U);throw"both async and sync fetching of the wasm failed";}catch(k){z(k)}}function Ma(){return da||!ea&&!Z||"function"!==typeof fetch?new Promise(function(a,c){a(wa())}):fetch(U,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+U+"'";return a.arrayBuffer()}).catch(function(){return wa()})}function ba(){if(!ba.strings){var a={USER:"web_user",
+LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:na},c;for(c in Aa)a[c]=Aa[c];var b=[];for(c in a)b.push(c+"="+a[c]);ba.strings=b}return ba.strings}function ma(k){function c(){if(!fa&&(fa=!0,!za)){Ba=!0;S(Ca);S(Da);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;)Ea.unshift(a.postRun.shift());
+S(Ea)}}if(!(0<aa)){if(a.preRun)for("function"==typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)Fa.unshift(a.preRun.shift());S(Fa);0<aa||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);c()},1)):c())}}function p(){}function u(a){return(a||p).__cache__}function N(a,c){var b=u(c),d=b[a];if(d)return d;d=Object.create((c||p).prototype);d.ptr=a;return b[a]=d}function V(a){if("string"===typeof a){for(var c=0,b=0;b<a.length;++b){var d=a.charCodeAt(b);
+55296<=d&&57343>=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++b)&1023);127>=d?++c:c=2047>=d?c+2:65535>=d?c+3:c+4}c=Array(c+1);b=0;d=c.length;if(0<d){d=b+d-1;for(var k=0;k<a.length;++k){var e=a.charCodeAt(k);if(55296<=e&&57343>=e){var f=a.charCodeAt(++k);e=65536+((e&1023)<<10)|f&1023}if(127>=e){if(b>=d)break;c[b++]=e}else{if(2047>=e){if(b+1>=d)break;c[b++]=192|e>>6}else{if(65535>=e){if(b+2>=d)break;c[b++]=224|e>>12}else{if(b+3>=d)break;c[b++]=240|e>>18;c[b++]=128|e>>12&63}c[b++]=128|e>>6&63}c[b++]=128|
+e&63}}c[b]=0}a=n.alloc(c,T);n.copy(c,T,a)}return a}function x(){throw"cannot construct a Status, no constructor in IDL";}function A(){this.ptr=Oa();u(A)[this.ptr]=this}function B(){this.ptr=Pa();u(B)[this.ptr]=this}function C(){this.ptr=Qa();u(C)[this.ptr]=this}function D(){this.ptr=Ra();u(D)[this.ptr]=this}function E(){this.ptr=Sa();u(E)[this.ptr]=this}function q(){this.ptr=Ta();u(q)[this.ptr]=this}function J(){this.ptr=Ua();u(J)[this.ptr]=this}function w(){this.ptr=Va();u(w)[this.ptr]=this}function F(){this.ptr=
+Wa();u(F)[this.ptr]=this}function r(){this.ptr=Xa();u(r)[this.ptr]=this}function G(){this.ptr=Ya();u(G)[this.ptr]=this}function H(){this.ptr=Za();u(H)[this.ptr]=this}function O(){this.ptr=$a();u(O)[this.ptr]=this}function K(){this.ptr=ab();u(K)[this.ptr]=this}function g(){this.ptr=bb();u(g)[this.ptr]=this}function y(){this.ptr=cb();u(y)[this.ptr]=this}function Q(){throw"cannot construct a VoidPtr, no constructor in IDL";}function I(){this.ptr=db();u(I)[this.ptr]=this}function L(){this.ptr=eb();u(L)[this.ptr]=
+this}m=m||{};var a="undefined"!==typeof m?m:{},Ga=!1,Ha=!1;a.onRuntimeInitialized=function(){Ga=!0;if(Ha&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.onModuleParsed=function(){Ha=!0;if(Ga&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.isVersionSupported=function(a){if("string"!==typeof a)return!1;a=a.split(".");return 2>a.length||3<a.length?!1:1==a[0]&&0<=a[1]&&3>=a[1]?!0:0!=a[0]||10<a[1]?!1:!0};var ha={},W;for(W in a)a.hasOwnProperty(W)&&(ha[W]=a[W]);var na="./this.program",
+ea=!1,Z=!1,oa=!1,fb=!1,Ia=!1;ea="object"===typeof window;Z="function"===typeof importScripts;oa=(fb="object"===typeof process&&"object"===typeof process.versions&&"string"===typeof process.versions.node)&&!ea&&!Z;Ia=!ea&&!oa&&!Z;var M="",pa,qa;if(oa){M=__dirname+"/";var ra=function(a,c){pa||(pa=require("fs"));qa||(qa=require("path"));a=qa.normalize(a);return pa.readFileSync(a,c?null:"utf8")};var la=function(a){a=ra(a,!0);a.buffer||(a=new Uint8Array(a));t(a.buffer);return a};1<process.argv.length&&
+(na=process.argv[1].replace(/\\/g,"/"));process.argv.slice(2);process.on("uncaughtException",function(a){throw a;});process.on("unhandledRejection",z);a.inspect=function(){return"[Emscripten Module object]"}}else if(Ia)"undefined"!=typeof read&&(ra=function(a){return read(a)}),la=function(a){if("function"===typeof readbuffer)return new Uint8Array(readbuffer(a));a=read(a,"binary");t("object"===typeof a);return a},"undefined"!==typeof print&&("undefined"===typeof console&&(console={}),console.log=print,
+console.warn=console.error="undefined"!==typeof printErr?printErr:print);else if(ea||Z)Z?M=self.location.href:document.currentScript&&(M=document.currentScript.src),f&&(M=f),M=0!==M.indexOf("blob:")?M.substr(0,M.lastIndexOf("/")+1):"",ra=function(a){var c=new XMLHttpRequest;c.open("GET",a,!1);c.send(null);return c.responseText},Z&&(la=function(a){var c=new XMLHttpRequest;c.open("GET",a,!1);c.responseType="arraybuffer";c.send(null);return new Uint8Array(c.response)});var ya=a.print||console.log.bind(console),
+Y=a.printErr||console.warn.bind(console);for(W in ha)ha.hasOwnProperty(W)&&(a[W]=ha[W]);ha=null;a.thisProgram&&(na=a.thisProgram);var da;a.wasmBinary&&(da=a.wasmBinary);"object"!==typeof WebAssembly&&Y("no native wasm support detected");var ia,gb=new WebAssembly.Table({initial:293,maximum:293,element:"anyfunc"}),za=!1,xa="undefined"!==typeof TextDecoder?new TextDecoder("utf8"):void 0;"undefined"!==typeof TextDecoder&&new TextDecoder("utf-16le");var T,ca,P,Ja=a.TOTAL_MEMORY||16777216;if(ia=a.wasmMemory?
+a.wasmMemory:new WebAssembly.Memory({initial:Ja/65536}))var ka=ia.buffer;Ja=ka.byteLength;l(ka);P[3416]=5256704;var Fa=[],Ca=[],Da=[],Ea=[],Ba=!1,aa=0,sa=null,ja=null;a.preloadedImages={};a.preloadedAudios={};var U="draco_decoder.wasm";va(U)||(U=v(U));Ca.push({func:function(){hb()}});var Aa={},R={buffers:[null,[],[]],printChar:function(a,c){var b=R.buffers[a];0===c||10===c?((1===a?ya:Y)(h(b,0)),b.length=0):b.push(c)},varargs:0,get:function(a){R.varargs+=4;return P[R.varargs-4>>2]},getStr:function(){return X(R.get())},
+get64:function(){var a=R.get();R.get();return a},getZero:function(){R.get()}},Ka={__cxa_allocate_exception:function(a){return ib(a)},__cxa_throw:function(a,c,b){"uncaught_exception"in ta?ta.uncaught_exceptions++:ta.uncaught_exceptions=1;throw a;},abort:function(){z()},emscripten_get_sbrk_ptr:function(){return 13664},emscripten_memcpy_big:function(a,c,b){ca.set(ca.subarray(c,c+b),a)},emscripten_resize_heap:function(a){if(2147418112<a)return!1;for(var c=Math.max(T.length,16777216);c<a;)c=536870912>=
+c?e(2*c,65536):Math.min(e((3*c+2147483648)/4,65536),2147418112);a:{try{ia.grow(c-ka.byteLength+65535>>16);l(ia.buffer);var b=1;break a}catch(d){}b=void 0}return b?!0:!1},environ_get:function(a,c){var b=0;ba().forEach(function(d,e){var f=c+b;e=P[a+4*e>>2]=f;for(f=0;f<d.length;++f)T[e++>>0]=d.charCodeAt(f);T[e>>0]=0;b+=d.length+1});return 0},environ_sizes_get:function(a,c){var b=ba();P[a>>2]=b.length;var d=0;b.forEach(function(a){d+=a.length+1});P[c>>2]=d;return 0},fd_close:function(a){return 0},fd_seek:function(a,
+c,b,d,e){return 0},fd_write:function(a,c,b,d){try{for(var e=0,f=0;f<b;f++){for(var g=P[c+8*f>>2],k=P[c+(8*f+4)>>2],h=0;h<k;h++)R.printChar(a,ca[g+h]);e+=k}P[d>>2]=e;return 0}catch(ua){return"undefined"!==typeof FS&&ua instanceof FS.ErrnoError||z(ua),ua.errno}},memory:ia,setTempRet0:function(a){},table:gb},La=function(){function e(c,b){a.asm=c.exports;aa--;a.monitorRunDependencies&&a.monitorRunDependencies(aa);0==aa&&(null!==sa&&(clearInterval(sa),sa=null),ja&&(c=ja,ja=null,c()))}function c(a){e(a.instance)}
+function b(a){return Ma().then(function(a){return WebAssembly.instantiate(a,d)}).then(a,function(a){Y("failed to asynchronously prepare wasm: "+a);z(a)})}var d={env:Ka,wasi_unstable:Ka};aa++;a.monitorRunDependencies&&a.monitorRunDependencies(aa);if(a.instantiateWasm)try{return a.instantiateWasm(d,e)}catch(Na){return Y("Module.instantiateWasm callback failed with error: "+Na),!1}(function(){if(da||"function"!==typeof WebAssembly.instantiateStreaming||va(U)||"function"!==typeof fetch)return b(c);fetch(U,
+{credentials:"same-origin"}).then(function(a){return WebAssembly.instantiateStreaming(a,d).then(c,function(a){Y("wasm streaming compile failed: "+a);Y("falling back to ArrayBuffer instantiation");b(c)})})})();return{}}();a.asm=La;var hb=a.___wasm_call_ctors=function(){return a.asm.__wasm_call_ctors.apply(null,arguments)},jb=a._emscripten_bind_Status_code_0=function(){return a.asm.emscripten_bind_Status_code_0.apply(null,arguments)},kb=a._emscripten_bind_Status_ok_0=function(){return a.asm.emscripten_bind_Status_ok_0.apply(null,
+arguments)},lb=a._emscripten_bind_Status_error_msg_0=function(){return a.asm.emscripten_bind_Status_error_msg_0.apply(null,arguments)},mb=a._emscripten_bind_Status___destroy___0=function(){return a.asm.emscripten_bind_Status___destroy___0.apply(null,arguments)},Oa=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=function(){return a.asm.emscripten_bind_DracoUInt16Array_DracoUInt16Array_0.apply(null,arguments)},nb=a._emscripten_bind_DracoUInt16Array_GetValue_1=function(){return a.asm.emscripten_bind_DracoUInt16Array_GetValue_1.apply(null,
+arguments)},ob=a._emscripten_bind_DracoUInt16Array_size_0=function(){return a.asm.emscripten_bind_DracoUInt16Array_size_0.apply(null,arguments)},pb=a._emscripten_bind_DracoUInt16Array___destroy___0=function(){return a.asm.emscripten_bind_DracoUInt16Array___destroy___0.apply(null,arguments)},Pa=a._emscripten_bind_PointCloud_PointCloud_0=function(){return a.asm.emscripten_bind_PointCloud_PointCloud_0.apply(null,arguments)},qb=a._emscripten_bind_PointCloud_num_attributes_0=function(){return a.asm.emscripten_bind_PointCloud_num_attributes_0.apply(null,
+arguments)},rb=a._emscripten_bind_PointCloud_num_points_0=function(){return a.asm.emscripten_bind_PointCloud_num_points_0.apply(null,arguments)},sb=a._emscripten_bind_PointCloud___destroy___0=function(){return a.asm.emscripten_bind_PointCloud___destroy___0.apply(null,arguments)},Qa=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=function(){return a.asm.emscripten_bind_DracoUInt8Array_DracoUInt8Array_0.apply(null,arguments)},tb=a._emscripten_bind_DracoUInt8Array_GetValue_1=function(){return a.asm.emscripten_bind_DracoUInt8Array_GetValue_1.apply(null,
+arguments)},ub=a._emscripten_bind_DracoUInt8Array_size_0=function(){return a.asm.emscripten_bind_DracoUInt8Array_size_0.apply(null,arguments)},vb=a._emscripten_bind_DracoUInt8Array___destroy___0=function(){return a.asm.emscripten_bind_DracoUInt8Array___destroy___0.apply(null,arguments)},Ra=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=function(){return a.asm.emscripten_bind_DracoUInt32Array_DracoUInt32Array_0.apply(null,arguments)},wb=a._emscripten_bind_DracoUInt32Array_GetValue_1=function(){return a.asm.emscripten_bind_DracoUInt32Array_GetValue_1.apply(null,
+arguments)},xb=a._emscripten_bind_DracoUInt32Array_size_0=function(){return a.asm.emscripten_bind_DracoUInt32Array_size_0.apply(null,arguments)},yb=a._emscripten_bind_DracoUInt32Array___destroy___0=function(){return a.asm.emscripten_bind_DracoUInt32Array___destroy___0.apply(null,arguments)},Sa=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=function(){return a.asm.emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0.apply(null,arguments)},zb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=
+function(){return a.asm.emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1.apply(null,arguments)},Ab=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=function(){return a.asm.emscripten_bind_AttributeOctahedronTransform_quantization_bits_0.apply(null,arguments)},Bb=a._emscripten_bind_AttributeOctahedronTransform___destroy___0=function(){return a.asm.emscripten_bind_AttributeOctahedronTransform___destroy___0.apply(null,arguments)},Ta=a._emscripten_bind_PointAttribute_PointAttribute_0=
+function(){return a.asm.emscripten_bind_PointAttribute_PointAttribute_0.apply(null,arguments)},Cb=a._emscripten_bind_PointAttribute_size_0=function(){return a.asm.emscripten_bind_PointAttribute_size_0.apply(null,arguments)},Db=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=function(){return a.asm.emscripten_bind_PointAttribute_GetAttributeTransformData_0.apply(null,arguments)},Eb=a._emscripten_bind_PointAttribute_attribute_type_0=function(){return a.asm.emscripten_bind_PointAttribute_attribute_type_0.apply(null,
+arguments)},Fb=a._emscripten_bind_PointAttribute_data_type_0=function(){return a.asm.emscripten_bind_PointAttribute_data_type_0.apply(null,arguments)},Gb=a._emscripten_bind_PointAttribute_num_components_0=function(){return a.asm.emscripten_bind_PointAttribute_num_components_0.apply(null,arguments)},Hb=a._emscripten_bind_PointAttribute_normalized_0=function(){return a.asm.emscripten_bind_PointAttribute_normalized_0.apply(null,arguments)},Ib=a._emscripten_bind_PointAttribute_byte_stride_0=function(){return a.asm.emscripten_bind_PointAttribute_byte_stride_0.apply(null,
+arguments)},Jb=a._emscripten_bind_PointAttribute_byte_offset_0=function(){return a.asm.emscripten_bind_PointAttribute_byte_offset_0.apply(null,arguments)},Kb=a._emscripten_bind_PointAttribute_unique_id_0=function(){return a.asm.emscripten_bind_PointAttribute_unique_id_0.apply(null,arguments)},Lb=a._emscripten_bind_PointAttribute___destroy___0=function(){return a.asm.emscripten_bind_PointAttribute___destroy___0.apply(null,arguments)},Ua=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=
+function(){return a.asm.emscripten_bind_AttributeTransformData_AttributeTransformData_0.apply(null,arguments)},Mb=a._emscripten_bind_AttributeTransformData_transform_type_0=function(){return a.asm.emscripten_bind_AttributeTransformData_transform_type_0.apply(null,arguments)},Nb=a._emscripten_bind_AttributeTransformData___destroy___0=function(){return a.asm.emscripten_bind_AttributeTransformData___destroy___0.apply(null,arguments)},Va=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=
+function(){return a.asm.emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0.apply(null,arguments)},Ob=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=function(){return a.asm.emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1.apply(null,arguments)},Pb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=function(){return a.asm.emscripten_bind_AttributeQuantizationTransform_quantization_bits_0.apply(null,arguments)},
+Qb=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=function(){return a.asm.emscripten_bind_AttributeQuantizationTransform_min_value_1.apply(null,arguments)},Rb=a._emscripten_bind_AttributeQuantizationTransform_range_0=function(){return a.asm.emscripten_bind_AttributeQuantizationTransform_range_0.apply(null,arguments)},Sb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=function(){return a.asm.emscripten_bind_AttributeQuantizationTransform___destroy___0.apply(null,arguments)},
+Wa=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=function(){return a.asm.emscripten_bind_DracoInt8Array_DracoInt8Array_0.apply(null,arguments)},Tb=a._emscripten_bind_DracoInt8Array_GetValue_1=function(){return a.asm.emscripten_bind_DracoInt8Array_GetValue_1.apply(null,arguments)},Ub=a._emscripten_bind_DracoInt8Array_size_0=function(){return a.asm.emscripten_bind_DracoInt8Array_size_0.apply(null,arguments)},Vb=a._emscripten_bind_DracoInt8Array___destroy___0=function(){return a.asm.emscripten_bind_DracoInt8Array___destroy___0.apply(null,
+arguments)},Xa=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=function(){return a.asm.emscripten_bind_MetadataQuerier_MetadataQuerier_0.apply(null,arguments)},Wb=a._emscripten_bind_MetadataQuerier_HasEntry_2=function(){return a.asm.emscripten_bind_MetadataQuerier_HasEntry_2.apply(null,arguments)},Xb=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=function(){return a.asm.emscripten_bind_MetadataQuerier_GetIntEntry_2.apply(null,arguments)},Yb=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=
+function(){return a.asm.emscripten_bind_MetadataQuerier_GetIntEntryArray_3.apply(null,arguments)},Zb=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=function(){return a.asm.emscripten_bind_MetadataQuerier_GetDoubleEntry_2.apply(null,arguments)},$b=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=function(){return a.asm.emscripten_bind_MetadataQuerier_GetStringEntry_2.apply(null,arguments)},ac=a._emscripten_bind_MetadataQuerier_NumEntries_1=function(){return a.asm.emscripten_bind_MetadataQuerier_NumEntries_1.apply(null,
+arguments)},bc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=function(){return a.asm.emscripten_bind_MetadataQuerier_GetEntryName_2.apply(null,arguments)},cc=a._emscripten_bind_MetadataQuerier___destroy___0=function(){return a.asm.emscripten_bind_MetadataQuerier___destroy___0.apply(null,arguments)},Ya=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=function(){return a.asm.emscripten_bind_DracoInt16Array_DracoInt16Array_0.apply(null,arguments)},dc=a._emscripten_bind_DracoInt16Array_GetValue_1=
+function(){return a.asm.emscripten_bind_DracoInt16Array_GetValue_1.apply(null,arguments)},ec=a._emscripten_bind_DracoInt16Array_size_0=function(){return a.asm.emscripten_bind_DracoInt16Array_size_0.apply(null,arguments)},fc=a._emscripten_bind_DracoInt16Array___destroy___0=function(){return a.asm.emscripten_bind_DracoInt16Array___destroy___0.apply(null,arguments)},Za=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=function(){return a.asm.emscripten_bind_DracoFloat32Array_DracoFloat32Array_0.apply(null,
+arguments)},gc=a._emscripten_bind_DracoFloat32Array_GetValue_1=function(){return a.asm.emscripten_bind_DracoFloat32Array_GetValue_1.apply(null,arguments)},hc=a._emscripten_bind_DracoFloat32Array_size_0=function(){return a.asm.emscripten_bind_DracoFloat32Array_size_0.apply(null,arguments)},ic=a._emscripten_bind_DracoFloat32Array___destroy___0=function(){return a.asm.emscripten_bind_DracoFloat32Array___destroy___0.apply(null,arguments)},$a=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=function(){return a.asm.emscripten_bind_GeometryAttribute_GeometryAttribute_0.apply(null,
+arguments)},jc=a._emscripten_bind_GeometryAttribute___destroy___0=function(){return a.asm.emscripten_bind_GeometryAttribute___destroy___0.apply(null,arguments)},ab=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=function(){return a.asm.emscripten_bind_DecoderBuffer_DecoderBuffer_0.apply(null,arguments)},kc=a._emscripten_bind_DecoderBuffer_Init_2=function(){return a.asm.emscripten_bind_DecoderBuffer_Init_2.apply(null,arguments)},lc=a._emscripten_bind_DecoderBuffer___destroy___0=function(){return a.asm.emscripten_bind_DecoderBuffer___destroy___0.apply(null,
+arguments)},bb=a._emscripten_bind_Decoder_Decoder_0=function(){return a.asm.emscripten_bind_Decoder_Decoder_0.apply(null,arguments)},mc=a._emscripten_bind_Decoder_GetEncodedGeometryType_1=function(){return a.asm.emscripten_bind_Decoder_GetEncodedGeometryType_1.apply(null,arguments)},nc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=function(){return a.asm.emscripten_bind_Decoder_DecodeBufferToPointCloud_2.apply(null,arguments)},oc=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=function(){return a.asm.emscripten_bind_Decoder_DecodeBufferToMesh_2.apply(null,
+arguments)},pc=a._emscripten_bind_Decoder_GetAttributeId_2=function(){return a.asm.emscripten_bind_Decoder_GetAttributeId_2.apply(null,arguments)},qc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=function(){return a.asm.emscripten_bind_Decoder_GetAttributeIdByName_2.apply(null,arguments)},rc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3.apply(null,arguments)},sc=a._emscripten_bind_Decoder_GetAttribute_2=
+function(){return a.asm.emscripten_bind_Decoder_GetAttribute_2.apply(null,arguments)},tc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=function(){return a.asm.emscripten_bind_Decoder_GetAttributeByUniqueId_2.apply(null,arguments)},uc=a._emscripten_bind_Decoder_GetMetadata_1=function(){return a.asm.emscripten_bind_Decoder_GetMetadata_1.apply(null,arguments)},vc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=function(){return a.asm.emscripten_bind_Decoder_GetAttributeMetadata_2.apply(null,
+arguments)},wc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=function(){return a.asm.emscripten_bind_Decoder_GetFaceFromMesh_3.apply(null,arguments)},xc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=function(){return a.asm.emscripten_bind_Decoder_GetTriangleStripsFromMesh_2.apply(null,arguments)},yc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=function(){return a.asm.emscripten_bind_Decoder_GetTrianglesUInt16Array_3.apply(null,arguments)},zc=a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=
+function(){return a.asm.emscripten_bind_Decoder_GetTrianglesUInt32Array_3.apply(null,arguments)},Ac=a._emscripten_bind_Decoder_GetAttributeFloat_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeFloat_3.apply(null,arguments)},Bc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3.apply(null,arguments)},Cc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeIntForAllPoints_3.apply(null,
+arguments)},Dc=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3.apply(null,arguments)},Ec=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3.apply(null,arguments)},Fc=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3.apply(null,arguments)},
+Gc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3.apply(null,arguments)},Hc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3.apply(null,arguments)},Ic=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=function(){return a.asm.emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3.apply(null,arguments)},Jc=
+a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=function(){return a.asm.emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5.apply(null,arguments)},Kc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=function(){return a.asm.emscripten_bind_Decoder_SkipAttributeTransform_1.apply(null,arguments)},Lc=a._emscripten_bind_Decoder___destroy___0=function(){return a.asm.emscripten_bind_Decoder___destroy___0.apply(null,arguments)},cb=a._emscripten_bind_Mesh_Mesh_0=function(){return a.asm.emscripten_bind_Mesh_Mesh_0.apply(null,
+arguments)},Mc=a._emscripten_bind_Mesh_num_faces_0=function(){return a.asm.emscripten_bind_Mesh_num_faces_0.apply(null,arguments)},Nc=a._emscripten_bind_Mesh_num_attributes_0=function(){return a.asm.emscripten_bind_Mesh_num_attributes_0.apply(null,arguments)},Oc=a._emscripten_bind_Mesh_num_points_0=function(){return a.asm.emscripten_bind_Mesh_num_points_0.apply(null,arguments)},Pc=a._emscripten_bind_Mesh___destroy___0=function(){return a.asm.emscripten_bind_Mesh___destroy___0.apply(null,arguments)},
+Qc=a._emscripten_bind_VoidPtr___destroy___0=function(){return a.asm.emscripten_bind_VoidPtr___destroy___0.apply(null,arguments)},db=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=function(){return a.asm.emscripten_bind_DracoInt32Array_DracoInt32Array_0.apply(null,arguments)},Rc=a._emscripten_bind_DracoInt32Array_GetValue_1=function(){return a.asm.emscripten_bind_DracoInt32Array_GetValue_1.apply(null,arguments)},Sc=a._emscripten_bind_DracoInt32Array_size_0=function(){return a.asm.emscripten_bind_DracoInt32Array_size_0.apply(null,
+arguments)},Tc=a._emscripten_bind_DracoInt32Array___destroy___0=function(){return a.asm.emscripten_bind_DracoInt32Array___destroy___0.apply(null,arguments)},eb=a._emscripten_bind_Metadata_Metadata_0=function(){return a.asm.emscripten_bind_Metadata_Metadata_0.apply(null,arguments)},Uc=a._emscripten_bind_Metadata___destroy___0=function(){return a.asm.emscripten_bind_Metadata___destroy___0.apply(null,arguments)},Vc=a._emscripten_enum_draco_StatusCode_OK=function(){return a.asm.emscripten_enum_draco_StatusCode_OK.apply(null,
+arguments)},Wc=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=function(){return a.asm.emscripten_enum_draco_StatusCode_DRACO_ERROR.apply(null,arguments)},Xc=a._emscripten_enum_draco_StatusCode_IO_ERROR=function(){return a.asm.emscripten_enum_draco_StatusCode_IO_ERROR.apply(null,arguments)},Yc=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=function(){return a.asm.emscripten_enum_draco_StatusCode_INVALID_PARAMETER.apply(null,arguments)},Zc=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=
+function(){return a.asm.emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION.apply(null,arguments)},$c=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=function(){return a.asm.emscripten_enum_draco_StatusCode_UNKNOWN_VERSION.apply(null,arguments)},ad=a._emscripten_enum_draco_DataType_DT_INVALID=function(){return a.asm.emscripten_enum_draco_DataType_DT_INVALID.apply(null,arguments)},bd=a._emscripten_enum_draco_DataType_DT_INT8=function(){return a.asm.emscripten_enum_draco_DataType_DT_INT8.apply(null,
+arguments)},cd=a._emscripten_enum_draco_DataType_DT_UINT8=function(){return a.asm.emscripten_enum_draco_DataType_DT_UINT8.apply(null,arguments)},dd=a._emscripten_enum_draco_DataType_DT_INT16=function(){return a.asm.emscripten_enum_draco_DataType_DT_INT16.apply(null,arguments)},ed=a._emscripten_enum_draco_DataType_DT_UINT16=function(){return a.asm.emscripten_enum_draco_DataType_DT_UINT16.apply(null,arguments)},fd=a._emscripten_enum_draco_DataType_DT_INT32=function(){return a.asm.emscripten_enum_draco_DataType_DT_INT32.apply(null,
+arguments)},gd=a._emscripten_enum_draco_DataType_DT_UINT32=function(){return a.asm.emscripten_enum_draco_DataType_DT_UINT32.apply(null,arguments)},hd=a._emscripten_enum_draco_DataType_DT_INT64=function(){return a.asm.emscripten_enum_draco_DataType_DT_INT64.apply(null,arguments)},id=a._emscripten_enum_draco_DataType_DT_UINT64=function(){return a.asm.emscripten_enum_draco_DataType_DT_UINT64.apply(null,arguments)},jd=a._emscripten_enum_draco_DataType_DT_FLOAT32=function(){return a.asm.emscripten_enum_draco_DataType_DT_FLOAT32.apply(null,
+arguments)},kd=a._emscripten_enum_draco_DataType_DT_FLOAT64=function(){return a.asm.emscripten_enum_draco_DataType_DT_FLOAT64.apply(null,arguments)},ld=a._emscripten_enum_draco_DataType_DT_BOOL=function(){return a.asm.emscripten_enum_draco_DataType_DT_BOOL.apply(null,arguments)},md=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=function(){return a.asm.emscripten_enum_draco_DataType_DT_TYPES_COUNT.apply(null,arguments)},nd=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=function(){return a.asm.emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE.apply(null,
+arguments)},od=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=function(){return a.asm.emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD.apply(null,arguments)},pd=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=function(){return a.asm.emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH.apply(null,arguments)},qd=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=function(){return a.asm.emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM.apply(null,
+arguments)},rd=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=function(){return a.asm.emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM.apply(null,arguments)},sd=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=function(){return a.asm.emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM.apply(null,arguments)},td=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=function(){return a.asm.emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM.apply(null,
+arguments)},ud=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=function(){return a.asm.emscripten_enum_draco_GeometryAttribute_Type_INVALID.apply(null,arguments)},vd=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=function(){return a.asm.emscripten_enum_draco_GeometryAttribute_Type_POSITION.apply(null,arguments)},wd=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=function(){return a.asm.emscripten_enum_draco_GeometryAttribute_Type_NORMAL.apply(null,arguments)},xd=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=
+function(){return a.asm.emscripten_enum_draco_GeometryAttribute_Type_COLOR.apply(null,arguments)},yd=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=function(){return a.asm.emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD.apply(null,arguments)},zd=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=function(){return a.asm.emscripten_enum_draco_GeometryAttribute_Type_GENERIC.apply(null,arguments)};a._setThrew=function(){return a.asm.setThrew.apply(null,arguments)};var ta=a.__ZSt18uncaught_exceptionv=
+function(){return a.asm._ZSt18uncaught_exceptionv.apply(null,arguments)};a._free=function(){return a.asm.free.apply(null,arguments)};var ib=a._malloc=function(){return a.asm.malloc.apply(null,arguments)};a.stackSave=function(){return a.asm.stackSave.apply(null,arguments)};a.stackAlloc=function(){return a.asm.stackAlloc.apply(null,arguments)};a.stackRestore=function(){return a.asm.stackRestore.apply(null,arguments)};a.__growWasmMemory=function(){return a.asm.__growWasmMemory.apply(null,arguments)};
+a.dynCall_ii=function(){return a.asm.dynCall_ii.apply(null,arguments)};a.dynCall_vi=function(){return a.asm.dynCall_vi.apply(null,arguments)};a.dynCall_iii=function(){return a.asm.dynCall_iii.apply(null,arguments)};a.dynCall_vii=function(){return a.asm.dynCall_vii.apply(null,arguments)};a.dynCall_iiii=function(){return a.asm.dynCall_iiii.apply(null,arguments)};a.dynCall_v=function(){return a.asm.dynCall_v.apply(null,arguments)};a.dynCall_viii=function(){return a.asm.dynCall_viii.apply(null,arguments)};
+a.dynCall_viiii=function(){return a.asm.dynCall_viiii.apply(null,arguments)};a.dynCall_iiiiiii=function(){return a.asm.dynCall_iiiiiii.apply(null,arguments)};a.dynCall_iidiiii=function(){return a.asm.dynCall_iidiiii.apply(null,arguments)};a.dynCall_jiji=function(){return a.asm.dynCall_jiji.apply(null,arguments)};a.dynCall_viiiiii=function(){return a.asm.dynCall_viiiiii.apply(null,arguments)};a.dynCall_viiiii=function(){return a.asm.dynCall_viiiii.apply(null,arguments)};a.asm=La;var fa;a.then=function(e){if(fa)e(a);
+else{var c=a.onRuntimeInitialized;a.onRuntimeInitialized=function(){c&&c();e(a)}}return a};ja=function c(){fa||ma();fa||(ja=c)};a.run=ma;if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();ma();p.prototype=Object.create(p.prototype);p.prototype.constructor=p;p.prototype.__class__=p;p.__cache__={};a.WrapperObject=p;a.getCache=u;a.wrapPointer=N;a.castObject=function(a,b){return N(a.ptr,b)};a.NULL=N(0);a.destroy=function(a){if(!a.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";
+a.__destroy__();delete u(a.__class__)[a.ptr]};a.compare=function(a,b){return a.ptr===b.ptr};a.getPointer=function(a){return a.ptr};a.getClass=function(a){return a.__class__};var n={buffer:0,size:0,pos:0,temps:[],needed:0,prepare:function(){if(n.needed){for(var c=0;c<n.temps.length;c++)a._free(n.temps[c]);n.temps.length=0;a._free(n.buffer);n.buffer=0;n.size+=n.needed;n.needed=0}n.buffer||(n.size+=128,n.buffer=a._malloc(n.size),t(n.buffer));n.pos=0},alloc:function(c,b){t(n.buffer);c=c.length*b.BYTES_PER_ELEMENT;
+c=c+7&-8;n.pos+c>=n.size?(t(0<c),n.needed+=c,b=a._malloc(c),n.temps.push(b)):(b=n.buffer+n.pos,n.pos+=c);return b},copy:function(a,b,d){switch(b.BYTES_PER_ELEMENT){case 2:d>>=1;break;case 4:d>>=2;break;case 8:d>>=3}for(var c=0;c<a.length;c++)b[d+c]=a[c]}};x.prototype=Object.create(p.prototype);x.prototype.constructor=x;x.prototype.__class__=x;x.__cache__={};a.Status=x;x.prototype.code=x.prototype.code=function(){return jb(this.ptr)};x.prototype.ok=x.prototype.ok=function(){return!!kb(this.ptr)};x.prototype.error_msg=
+x.prototype.error_msg=function(){return X(lb(this.ptr))};x.prototype.__destroy__=x.prototype.__destroy__=function(){mb(this.ptr)};A.prototype=Object.create(p.prototype);A.prototype.constructor=A;A.prototype.__class__=A;A.__cache__={};a.DracoUInt16Array=A;A.prototype.GetValue=A.prototype.GetValue=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return nb(c,a)};A.prototype.size=A.prototype.size=function(){return ob(this.ptr)};A.prototype.__destroy__=A.prototype.__destroy__=function(){pb(this.ptr)};
+B.prototype=Object.create(p.prototype);B.prototype.constructor=B;B.prototype.__class__=B;B.__cache__={};a.PointCloud=B;B.prototype.num_attributes=B.prototype.num_attributes=function(){return qb(this.ptr)};B.prototype.num_points=B.prototype.num_points=function(){return rb(this.ptr)};B.prototype.__destroy__=B.prototype.__destroy__=function(){sb(this.ptr)};C.prototype=Object.create(p.prototype);C.prototype.constructor=C;C.prototype.__class__=C;C.__cache__={};a.DracoUInt8Array=C;C.prototype.GetValue=
+C.prototype.GetValue=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return tb(c,a)};C.prototype.size=C.prototype.size=function(){return ub(this.ptr)};C.prototype.__destroy__=C.prototype.__destroy__=function(){vb(this.ptr)};D.prototype=Object.create(p.prototype);D.prototype.constructor=D;D.prototype.__class__=D;D.__cache__={};a.DracoUInt32Array=D;D.prototype.GetValue=D.prototype.GetValue=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return wb(c,a)};D.prototype.size=D.prototype.size=
+function(){return xb(this.ptr)};D.prototype.__destroy__=D.prototype.__destroy__=function(){yb(this.ptr)};E.prototype=Object.create(p.prototype);E.prototype.constructor=E;E.prototype.__class__=E;E.__cache__={};a.AttributeOctahedronTransform=E;E.prototype.InitFromAttribute=E.prototype.InitFromAttribute=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return!!zb(c,a)};E.prototype.quantization_bits=E.prototype.quantization_bits=function(){return Ab(this.ptr)};E.prototype.__destroy__=E.prototype.__destroy__=
+function(){Bb(this.ptr)};q.prototype=Object.create(p.prototype);q.prototype.constructor=q;q.prototype.__class__=q;q.__cache__={};a.PointAttribute=q;q.prototype.size=q.prototype.size=function(){return Cb(this.ptr)};q.prototype.GetAttributeTransformData=q.prototype.GetAttributeTransformData=function(){return N(Db(this.ptr),J)};q.prototype.attribute_type=q.prototype.attribute_type=function(){return Eb(this.ptr)};q.prototype.data_type=q.prototype.data_type=function(){return Fb(this.ptr)};q.prototype.num_components=
+q.prototype.num_components=function(){return Gb(this.ptr)};q.prototype.normalized=q.prototype.normalized=function(){return!!Hb(this.ptr)};q.prototype.byte_stride=q.prototype.byte_stride=function(){return Ib(this.ptr)};q.prototype.byte_offset=q.prototype.byte_offset=function(){return Jb(this.ptr)};q.prototype.unique_id=q.prototype.unique_id=function(){return Kb(this.ptr)};q.prototype.__destroy__=q.prototype.__destroy__=function(){Lb(this.ptr)};J.prototype=Object.create(p.prototype);J.prototype.constructor=
+J;J.prototype.__class__=J;J.__cache__={};a.AttributeTransformData=J;J.prototype.transform_type=J.prototype.transform_type=function(){return Mb(this.ptr)};J.prototype.__destroy__=J.prototype.__destroy__=function(){Nb(this.ptr)};w.prototype=Object.create(p.prototype);w.prototype.constructor=w;w.prototype.__class__=w;w.__cache__={};a.AttributeQuantizationTransform=w;w.prototype.InitFromAttribute=w.prototype.InitFromAttribute=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return!!Ob(c,a)};
+w.prototype.quantization_bits=w.prototype.quantization_bits=function(){return Pb(this.ptr)};w.prototype.min_value=w.prototype.min_value=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return Qb(c,a)};w.prototype.range=w.prototype.range=function(){return Rb(this.ptr)};w.prototype.__destroy__=w.prototype.__destroy__=function(){Sb(this.ptr)};F.prototype=Object.create(p.prototype);F.prototype.constructor=F;F.prototype.__class__=F;F.__cache__={};a.DracoInt8Array=F;F.prototype.GetValue=F.prototype.GetValue=
+function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return Tb(c,a)};F.prototype.size=F.prototype.size=function(){return Ub(this.ptr)};F.prototype.__destroy__=F.prototype.__destroy__=function(){Vb(this.ptr)};r.prototype=Object.create(p.prototype);r.prototype.constructor=r;r.prototype.__class__=r;r.__cache__={};a.MetadataQuerier=r;r.prototype.HasEntry=r.prototype.HasEntry=function(a,b){var c=this.ptr;n.prepare();a&&"object"===typeof a&&(a=a.ptr);b=b&&"object"===typeof b?b.ptr:V(b);return!!Wb(c,
+a,b)};r.prototype.GetIntEntry=r.prototype.GetIntEntry=function(a,b){var c=this.ptr;n.prepare();a&&"object"===typeof a&&(a=a.ptr);b=b&&"object"===typeof b?b.ptr:V(b);return Xb(c,a,b)};r.prototype.GetIntEntryArray=r.prototype.GetIntEntryArray=function(a,b,d){var c=this.ptr;n.prepare();a&&"object"===typeof a&&(a=a.ptr);b=b&&"object"===typeof b?b.ptr:V(b);d&&"object"===typeof d&&(d=d.ptr);Yb(c,a,b,d)};r.prototype.GetDoubleEntry=r.prototype.GetDoubleEntry=function(a,b){var c=this.ptr;n.prepare();a&&"object"===
+typeof a&&(a=a.ptr);b=b&&"object"===typeof b?b.ptr:V(b);return Zb(c,a,b)};r.prototype.GetStringEntry=r.prototype.GetStringEntry=function(a,b){var c=this.ptr;n.prepare();a&&"object"===typeof a&&(a=a.ptr);b=b&&"object"===typeof b?b.ptr:V(b);return X($b(c,a,b))};r.prototype.NumEntries=r.prototype.NumEntries=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return ac(c,a)};r.prototype.GetEntryName=r.prototype.GetEntryName=function(a,b){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===
+typeof b&&(b=b.ptr);return X(bc(c,a,b))};r.prototype.__destroy__=r.prototype.__destroy__=function(){cc(this.ptr)};G.prototype=Object.create(p.prototype);G.prototype.constructor=G;G.prototype.__class__=G;G.__cache__={};a.DracoInt16Array=G;G.prototype.GetValue=G.prototype.GetValue=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return dc(c,a)};G.prototype.size=G.prototype.size=function(){return ec(this.ptr)};G.prototype.__destroy__=G.prototype.__destroy__=function(){fc(this.ptr)};H.prototype=
+Object.create(p.prototype);H.prototype.constructor=H;H.prototype.__class__=H;H.__cache__={};a.DracoFloat32Array=H;H.prototype.GetValue=H.prototype.GetValue=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return gc(c,a)};H.prototype.size=H.prototype.size=function(){return hc(this.ptr)};H.prototype.__destroy__=H.prototype.__destroy__=function(){ic(this.ptr)};O.prototype=Object.create(p.prototype);O.prototype.constructor=O;O.prototype.__class__=O;O.__cache__={};a.GeometryAttribute=O;O.prototype.__destroy__=
+O.prototype.__destroy__=function(){jc(this.ptr)};K.prototype=Object.create(p.prototype);K.prototype.constructor=K;K.prototype.__class__=K;K.__cache__={};a.DecoderBuffer=K;K.prototype.Init=K.prototype.Init=function(a,b){var c=this.ptr;n.prepare();if("object"==typeof a&&"object"===typeof a){var e=n.alloc(a,T);n.copy(a,T,e);a=e}b&&"object"===typeof b&&(b=b.ptr);kc(c,a,b)};K.prototype.__destroy__=K.prototype.__destroy__=function(){lc(this.ptr)};g.prototype=Object.create(p.prototype);g.prototype.constructor=
+g;g.prototype.__class__=g;g.__cache__={};a.Decoder=g;g.prototype.GetEncodedGeometryType=g.prototype.GetEncodedGeometryType=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return mc(c,a)};g.prototype.DecodeBufferToPointCloud=g.prototype.DecodeBufferToPointCloud=function(a,b){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);return N(nc(c,a,b),x)};g.prototype.DecodeBufferToMesh=g.prototype.DecodeBufferToMesh=function(a,b){var c=this.ptr;a&&"object"===typeof a&&
+(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);return N(oc(c,a,b),x)};g.prototype.GetAttributeId=g.prototype.GetAttributeId=function(a,b){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);return pc(c,a,b)};g.prototype.GetAttributeIdByName=g.prototype.GetAttributeIdByName=function(a,b){var c=this.ptr;n.prepare();a&&"object"===typeof a&&(a=a.ptr);b=b&&"object"===typeof b?b.ptr:V(b);return qc(c,a,b)};g.prototype.GetAttributeIdByMetadataEntry=g.prototype.GetAttributeIdByMetadataEntry=
+function(a,b,d){var c=this.ptr;n.prepare();a&&"object"===typeof a&&(a=a.ptr);b=b&&"object"===typeof b?b.ptr:V(b);d=d&&"object"===typeof d?d.ptr:V(d);return rc(c,a,b,d)};g.prototype.GetAttribute=g.prototype.GetAttribute=function(a,b){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);return N(sc(c,a,b),q)};g.prototype.GetAttributeByUniqueId=g.prototype.GetAttributeByUniqueId=function(a,b){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);
+return N(tc(c,a,b),q)};g.prototype.GetMetadata=g.prototype.GetMetadata=function(a){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return N(uc(c,a),L)};g.prototype.GetAttributeMetadata=g.prototype.GetAttributeMetadata=function(a,b){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);return N(vc(c,a,b),L)};g.prototype.GetFaceFromMesh=g.prototype.GetFaceFromMesh=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===
+typeof d&&(d=d.ptr);return!!wc(c,a,b,d)};g.prototype.GetTriangleStripsFromMesh=g.prototype.GetTriangleStripsFromMesh=function(a,b){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);return xc(c,a,b)};g.prototype.GetTrianglesUInt16Array=g.prototype.GetTrianglesUInt16Array=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!yc(c,a,b,d)};g.prototype.GetTrianglesUInt32Array=g.prototype.GetTrianglesUInt32Array=
+function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!zc(c,a,b,d)};g.prototype.GetAttributeFloat=g.prototype.GetAttributeFloat=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ac(c,a,b,d)};g.prototype.GetAttributeFloatForAllPoints=g.prototype.GetAttributeFloatForAllPoints=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&
+(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Bc(c,a,b,d)};g.prototype.GetAttributeIntForAllPoints=g.prototype.GetAttributeIntForAllPoints=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Cc(c,a,b,d)};g.prototype.GetAttributeInt8ForAllPoints=g.prototype.GetAttributeInt8ForAllPoints=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&
+(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Dc(c,a,b,d)};g.prototype.GetAttributeUInt8ForAllPoints=g.prototype.GetAttributeUInt8ForAllPoints=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ec(c,a,b,d)};g.prototype.GetAttributeInt16ForAllPoints=g.prototype.GetAttributeInt16ForAllPoints=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&
+(d=d.ptr);return!!Fc(c,a,b,d)};g.prototype.GetAttributeUInt16ForAllPoints=g.prototype.GetAttributeUInt16ForAllPoints=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Gc(c,a,b,d)};g.prototype.GetAttributeInt32ForAllPoints=g.prototype.GetAttributeInt32ForAllPoints=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Hc(c,
+a,b,d)};g.prototype.GetAttributeUInt32ForAllPoints=g.prototype.GetAttributeUInt32ForAllPoints=function(a,b,d){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ic(c,a,b,d)};g.prototype.GetAttributeDataArrayForAllPoints=g.prototype.GetAttributeDataArrayForAllPoints=function(a,b,d,e,f){var c=this.ptr;a&&"object"===typeof a&&(a=a.ptr);b&&"object"===typeof b&&(b=b.ptr);d&&"object"===typeof d&&(d=d.ptr);e&&"object"===typeof e&&
+(e=e.ptr);f&&"object"===typeof f&&(f=f.ptr);return!!Jc(c,a,b,d,e,f)};g.prototype.SkipAttributeTransform=g.prototype.SkipAttributeTransform=function(a){var b=this.ptr;a&&"object"===typeof a&&(a=a.ptr);Kc(b,a)};g.prototype.__destroy__=g.prototype.__destroy__=function(){Lc(this.ptr)};y.prototype=Object.create(p.prototype);y.prototype.constructor=y;y.prototype.__class__=y;y.__cache__={};a.Mesh=y;y.prototype.num_faces=y.prototype.num_faces=function(){return Mc(this.ptr)};y.prototype.num_attributes=y.prototype.num_attributes=
+function(){return Nc(this.ptr)};y.prototype.num_points=y.prototype.num_points=function(){return Oc(this.ptr)};y.prototype.__destroy__=y.prototype.__destroy__=function(){Pc(this.ptr)};Q.prototype=Object.create(p.prototype);Q.prototype.constructor=Q;Q.prototype.__class__=Q;Q.__cache__={};a.VoidPtr=Q;Q.prototype.__destroy__=Q.prototype.__destroy__=function(){Qc(this.ptr)};I.prototype=Object.create(p.prototype);I.prototype.constructor=I;I.prototype.__class__=I;I.__cache__={};a.DracoInt32Array=I;I.prototype.GetValue=
+I.prototype.GetValue=function(a){var b=this.ptr;a&&"object"===typeof a&&(a=a.ptr);return Rc(b,a)};I.prototype.size=I.prototype.size=function(){return Sc(this.ptr)};I.prototype.__destroy__=I.prototype.__destroy__=function(){Tc(this.ptr)};L.prototype=Object.create(p.prototype);L.prototype.constructor=L;L.prototype.__class__=L;L.__cache__={};a.Metadata=L;L.prototype.__destroy__=L.prototype.__destroy__=function(){Uc(this.ptr)};(function(){function c(){a.OK=Vc();a.DRACO_ERROR=Wc();a.IO_ERROR=Xc();a.INVALID_PARAMETER=
+Yc();a.UNSUPPORTED_VERSION=Zc();a.UNKNOWN_VERSION=$c();a.DT_INVALID=ad();a.DT_INT8=bd();a.DT_UINT8=cd();a.DT_INT16=dd();a.DT_UINT16=ed();a.DT_INT32=fd();a.DT_UINT32=gd();a.DT_INT64=hd();a.DT_UINT64=id();a.DT_FLOAT32=jd();a.DT_FLOAT64=kd();a.DT_BOOL=ld();a.DT_TYPES_COUNT=md();a.INVALID_GEOMETRY_TYPE=nd();a.POINT_CLOUD=od();a.TRIANGULAR_MESH=pd();a.ATTRIBUTE_INVALID_TRANSFORM=qd();a.ATTRIBUTE_NO_TRANSFORM=rd();a.ATTRIBUTE_QUANTIZATION_TRANSFORM=sd();a.ATTRIBUTE_OCTAHEDRON_TRANSFORM=td();a.INVALID=ud();
+a.POSITION=vd();a.NORMAL=wd();a.COLOR=xd();a.TEX_COORD=yd();a.GENERIC=zd()}Ba?c():Da.unshift(c)})();if("function"===typeof a.onModuleParsed)a.onModuleParsed();return m}}();"object"===typeof exports&&"object"===typeof module?module.exports=DracoDecoderModule:"function"===typeof define&&define.amd?define([],function(){return DracoDecoderModule}):"object"===typeof exports&&(exports.DracoDecoderModule=DracoDecoderModule);

BIN
public/model/glft/cf/lmcf.glb


BIN
public/model/glft/cf/zdcf.glb


BIN
public/model/glft/fc/ddFc.glb


BIN
public/model/glft/fc/fc.glb


BIN
public/model/glft/fc/sdFc.glb


BIN
public/model/glft/fm/fm.glb


BIN
public/model/glft/jbfj/jbfj_fc.glb


BIN
public/model/glft/jbfj/jbfj_fm.glb


BIN
public/model/glft/jbfj/jbfj_hd.glb


+ 12 - 9
src/App.vue

@@ -1,28 +1,31 @@
 <template>
-  <ConfigProvider :locale="getAntdLocale">
-    <AppProvider>
-      <RouterView />
-    </AppProvider>
-  </ConfigProvider>
+  <AdaptiveContainer :options="{ width: width, height: height }">
+    <ConfigProvider :locale="getAntdLocale">
+      <AppProvider>
+        <RouterView />
+      </AppProvider>
+    </ConfigProvider>
+  </AdaptiveContainer>
 </template>
 
 <script lang="ts" setup>
+  import { ref } from 'vue';
   import { ConfigProvider } from 'ant-design-vue';
   import { AppProvider } from '/@/components/Application';
   import { useTitle } from '/@/hooks/web/useTitle';
   import { useLocale } from '/@/locales/useLocale';
-  // import { initModalWorker } from '/@/utils/threejs/main.worker';
+  import AdaptiveContainer from '/@/components/Container/src/Adaptive.vue';
 
   // support Multi-language
   const { getAntdLocale } = useLocale();
-
+  const width = ref(document.body.clientWidth);
+  const height = ref(document.body.clientHeight);
   // initModalWorker()
 
   useTitle();
 </script>
 <style lang="less" scoped>
-  #app{
+  #app {
     overflow: hidden;
   }
 </style>
-

+ 2 - 2
src/assets/less/modal.less

@@ -221,12 +221,12 @@
   .tabs-box {
     position: fixed;
     bottom: 0;
-    height: 30vh;
+    height: 300px;
     pointer-events: auto;
     background-color: #ffffff11;
     backdrop-filter: blur(10px);
     .tab-item {
-      height: calc(30vh - 50px);
+      height: 240px;
       color: #fff;
     }
   }

+ 164 - 0
src/components/Container/src/Adaptive.vue

@@ -0,0 +1,164 @@
+<template>
+  <div id="adaptive-container" :ref="refName">
+    <template v-if="ready">
+      <slot></slot>
+    </template>
+  </div>
+</template>
+
+<script>
+  import { ref, onMounted, onUnmounted, nextTick, defineComponent } from 'vue';
+  import { debounce, setRem } from '/@/utils/index';
+
+  export default defineComponent({
+    name: 'AdaptiveContainer',
+    props: {
+      options: Object,
+    },
+    setup(ctx) {
+      const refName = 'AdaptiveContainer'; //AdaptiveContainer
+      // 屏幕宽度
+      const width = ref(0);
+      // 屏幕高度
+      const height = ref(0);
+      // 原始屏幕宽度
+      const originalWidth = ref(0);
+      // 原始屏幕高度
+      const originalHeight = ref(0);
+      // 控制显示
+      const ready = ref(false);
+      /*
+       * dom:well-container的dom
+       * observer: window.MutationObserver(Bom实例)监听dom改变
+       */
+      let dom, observer;
+
+      //设置初始值
+      const initSize = () => {
+        return new Promise((resolve) => {
+          nextTick(() => {
+            dom = document.getElementById('adaptive-container');
+            // 获取大屏的传入尺寸
+            if (ctx.options && ctx.options.width && ctx.options.height) {
+              //传入宽高
+              width.value = ctx.options.width;
+              height.value = ctx.options.height;
+            } else {
+              //可见宽高
+              width.value = dom.clientWidth;
+              height.value = dom.clientHeight;
+            }
+            // 获取画布尺寸
+            if (!originalWidth.value || !originalHeight.value) {
+              //屏幕分辨率宽高
+              originalWidth.value = window.screen.width;
+              originalHeight.value = window.screen.height;
+            }
+            resolve();
+          });
+        });
+      };
+
+      const updateSize = () => {
+        if (width.value && height.value) {
+          dom.style.width = `${width.value}px`;
+          dom.style.height = `${height.value}px`;
+        } else {
+          dom.style.width = `${originalWidth.value}px`;
+          dom.style.height = `${originalHeight.value}px`;
+        }
+      };
+
+      const updateScale = () => {
+        // 获取真实的视口尺寸
+        const currentWidth = document.body.clientWidth;
+        const currentHeight = document.body.clientHeight;
+        // 获取大屏最终的宽高
+        const realWidth = width.value || originalWidth.value;
+        const realHeight = height.value || originalHeight.value;
+        // console.log(currentWidth, currentHeight)
+        // 缩放比例  = 分辨率宽高 / 传入宽高(可视宽高)
+        const widthScale = currentWidth / realWidth;
+        const heightScale = currentHeight / realHeight;
+        //如果dom存在,就按照比例缩放
+        dom && (dom.style.transform = `scale(${widthScale}, ${heightScale})`);
+      };
+      const cssSize = () => {
+        const currentWidth = document.body.clientWidth;
+        const realWidth = width.value || originalWidth.value;
+        const whdef = 100 / 1920; // 表示1920的设计图,使用100PX的默认值
+        const wW = currentWidth / realWidth; // 当前窗口的宽度
+        const rem = wW * whdef; // 以默认比例值乘以当前窗口宽度,得到该宽度下的相应FONT-SIZE值
+        document.documentElement.style.fontSize = rem + 'px';
+        window.addEventListener('resize', function () {
+          const whdef = 100 / 1920; // 表示1920的设计图,使用100PX的默认值
+          const wW = window.innerWidth; // 当前窗口的宽度
+          const rem = wW * whdef; // 以默认比例值乘以当前窗口宽度,得到该宽度下的相应FONT-SIZE值
+          document.documentElement.style.fontSize = rem + 'px';
+        });
+      };
+
+      //重置缩放比例
+      const onResize = async () => {
+        await initSize();
+        updateScale();
+        setRem();
+        cssSize();
+      };
+
+      const initMutationObserver = () => {
+        //监听元素属性变化
+        const MutationObserver = window.MutationObserver;
+        //如果变化,就用onResize重置屏幕所缩放比例
+        observer = new MutationObserver(onResize);
+        observer.observe(dom, {
+          attributes: true,
+          attributeFilter: ['style'],
+          attributeOldValue: true,
+        });
+      };
+      //移除监听属性
+      const removeMutationObserver = () => {
+        if (observer) {
+          observer.disconnect();
+          observer.takeRecords();
+          observer = null;
+        }
+      };
+      //
+      onMounted(async () => {
+        ready.value = false;
+
+        await initSize();
+        updateSize();
+        updateScale();
+        setRem();
+        cssSize();
+        window.addEventListener('resize', debounce(100, onResize));
+        initMutationObserver();
+        ready.value = true;
+      });
+
+      onUnmounted(() => {
+        window.removeEventListener('resize', onResize);
+        removeMutationObserver();
+      });
+
+      return {
+        refName,
+        ready,
+      };
+    },
+  });
+</script>
+
+<style lang="less">
+  #well-container {
+    position: fixed;
+    top: 0;
+    left: 0;
+    overflow: hidden;
+    transform-origin: left top;
+    z-index: 999;
+  }
+</style>

+ 57 - 0
src/hooks/core/threejs/RafHelper.ts

@@ -0,0 +1,57 @@
+/**
+ * This class implemented setTimeout and setInterval using RequestAnimationFrame
+ */
+export default class RafHelper {
+  readonly TIMEOUT = 'timeout';
+  readonly INTERVAL = 'interval';
+  private timeoutMap: any = {}; // timeout map, key is symbol
+  private intervalMap: any = {}; // interval map
+
+  private run(type = this.INTERVAL, cb: () => void, interval = 16.7) {
+    const now = Date.now;
+    let startTime = now();
+    let endTime = startTime;
+    const timerSymbol = Symbol('');
+    const loop = () => {
+      this.setIdMap(timerSymbol, type, loop);
+      endTime = now();
+      if (endTime - startTime >= interval) {
+        if (type === this.intervalMap) {
+          startTime = now();
+          endTime = startTime;
+        }
+        cb();
+        if (type === this.TIMEOUT) {
+          this.clearTimeout(timerSymbol);
+        }
+      }
+    };
+    this.setIdMap(timerSymbol, type, loop);
+    return timerSymbol;
+  }
+
+  private setIdMap(timerSymbol: symbol, type: string, loop: (time: number) => void) {
+    const id = requestAnimationFrame(loop);
+    if (type === this.INTERVAL) {
+      this.intervalMap[timerSymbol] = id;
+    } else if (type === this.TIMEOUT) {
+      this.timeoutMap[timerSymbol] = id;
+    }
+  }
+
+  public setTimeout(cb: () => void, interval: number) {
+    return this.run(this.TIMEOUT, cb, interval);
+  }
+
+  public clearTimeout(timer: symbol) {
+    cancelAnimationFrame(this.timeoutMap[timer]);
+  }
+
+  public setInterval(cb: () => void, interval: number) {
+    return this.run(this.INTERVAL, cb, interval);
+  }
+
+  public clearInterval(timer: symbol) {
+    cancelAnimationFrame(this.intervalMap[timer]);
+  }
+}

+ 19 - 36
src/hooks/core/useThree.ts → src/hooks/core/threejs/useThree.ts

@@ -115,7 +115,7 @@ class UseThree {
 
     this.renderer.shadowMap.enabled = true;
 
-    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+    // this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
 
     // 曝光程度
     this.renderer.toneMappingExposure = 1;
@@ -135,7 +135,6 @@ class UseThree {
 
   initControles() {
     this.orbitControls = this.track(new OrbitControls(this.camera as THREE.Camera, this.renderer?.domElement)) as OrbitControls;
-    this.orbitControls.update();
     // this.orbitControls.enableDamping = true;
   }
 
@@ -174,14 +173,15 @@ class UseThree {
                     obj.material.emissiveIntensity = 1;
                     obj.material.emissiveMap = obj.material.map;
                     obj.material.blending = THREE.CustomBlending;
-                    if (obj.name !== 'buxiugangse') {
-                      obj.receiveShadow = true;
-                    }
-                    obj.castShadow = true;
+
+                    // if (obj.name !== 'buxiugangse') {
+                    //   obj.receiveShadow = true;
+                    // }
+                    // obj.castShadow = true;
                   }
                 });
                 object.name = modalName;
-                this.scene?.add(object);
+
                 console.log('模型渲染时间', object, new Date().getTime() - b);
 
                 resolve(gltf);
@@ -202,7 +202,7 @@ class UseThree {
   }
 
   setTestPlane() {
-    const plane = this.track(new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, specular: 0x101010 })));
+    const plane = this.track(new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshPhongMaterial({ color: 0x999999, specular: 0x101010 })));
     plane.rotation.x = -Math.PI / 2;
     plane.position.y = 0.03;
     plane.receiveShadow = true;
@@ -217,41 +217,30 @@ class UseThree {
     this.composer.addPass(FXAAShaderPass);
   }
 
-  /* 自定义材质 */
-  setCustomMaterial(group) {}
-
   /* 场景环境背景 */
   setEnvMap(hdr) {
-    // new RGBELoader().setPath('/public/model/hdr/').load(hdr + '.jpeg', (texture) => {
-    //   debugger
-    //   texture.mapping = THREE.EquirectangularReflectionMapping;
-    //   (this.scene as THREE.Scene).background = texture;
-    //   (this.scene as THREE.Scene).environment = texture;
-    // });
+    const pmremGenerator = new THREE.PMREMGenerator(this.renderer as THREE.WebGLRenderer); // 使用hdr作为背景色
+    pmremGenerator.compileEquirectangularShader();
     this.track(new THREE.TextureLoader())
       .setPath('/model/hdr/')
       .load(hdr + '.jpeg', (texture) => {
-        this.track(texture);
         texture.mapping = THREE.EquirectangularReflectionMapping;
-        // (this.scene as THREE.Scene).background = texture;
-        (this.scene as THREE.Scene).environment = texture;
+        const envMap = pmremGenerator.fromEquirectangular(texture).texture;
+        (this.scene as THREE.Scene).environment = envMap;
+        pmremGenerator.dispose();
       });
   }
 
   render() {
-    this.camera?.updateMatrixWorld();
     this.stats?.update();
-    this.resetLookAt();
-    this.startMY();
     this.startAnimation();
     this.orbitControls?.update();
-    this.composer?.render();
-
+    this.camera?.updateMatrixWorld();
+    // this.composer?.render();
     // this.css3dRender?.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera)
+    this.renderer?.render(this.scene as THREE.Object3D, this.camera as THREE.Camera);
   }
 
-  /* 实时物体刷新朝向 */
-  resetLookAt() {}
   /* 漫游 */
   startMY() {}
   /* 初始动画 */
@@ -262,15 +251,8 @@ class UseThree {
       setTimeout(() => {
         this.animationId = requestAnimationFrame(this.animate.bind(this));
       }, 1000 / 30);
-      this.renderer?.render(this.scene as THREE.Object3D, this.camera as THREE.Camera);
+
       this.render();
-      // this.animationId = requestAnimationFrame(this.animate.bind(this));
-      // const T = this.clock.getDelta();
-      // this.timeS += T;
-      // if (this.timeS > 1 / this.FPS) {
-      //   this.render();
-      //   this.timeS = 0;
-      // }
     }
   }
 
@@ -367,13 +349,14 @@ class UseThree {
     if (this.canvasContainer) this.canvasContainer.innerHTML = '';
     if (this.CSSCanvasContainer) this.CSSCanvasContainer.innerHTML = '';
 
+    this.resourceTracker && this.resourceTracker.dispose();
+
     !!this.scene?.clear ?? this.scene?.clear();
     cancelAnimationFrame(this.animationId);
     this.animationId = -1;
     this.stats = null;
     this.scene = null;
     THREE.Cache.clear();
-    console.log('3D环境已清理干净');
   }
 }
 

+ 5 - 1
src/hooks/setting/useMenuSetting.ts

@@ -30,7 +30,11 @@ export function useMenuSetting() {
 
   const getMenuHidden = computed(() => appStore.getMenuSetting.hidden);
 
-  const getMenuWidth = computed(() => appStore.getMenuSetting.menuWidth);
+  const getMenuWidth = computed(() => {
+    const scale = document.body.clientWidth / 1920;
+    const menuWidth = appStore.getMenuSetting.menuWidth;
+    return menuWidth * scale;
+  });
 
   const getTrigger = computed(() => appStore.getMenuSetting.trigger);
 

+ 0 - 1
src/logics/initAppConfig.ts

@@ -62,7 +62,6 @@ export function initAppConfigStore() {
   // init store
   localeStore.initLocale();
 
-
   setTimeout(() => {
     clearObsoleteStorage();
   }, 16);

+ 0 - 2
src/main.ts

@@ -19,7 +19,6 @@ import { registerGlobComp } from '/@/components/registerGlobComp';
 import { registerThirdComp } from '/@/settings/registerThirdComp';
 import { useSso } from '/@/hooks/web/useSso';
 import { registerPackages } from '/@/utils/monorepo/registerPackages';
-
 // 在本地开发中引入的,以提高浏览器响应速度
 if (import.meta.env.DEV) {
   import('ant-design-vue/dist/antd.less');
@@ -63,7 +62,6 @@ async function bootstrap() {
 
   // 当路由准备好时在执行挂载( https://next.router.vuejs.org/api/#isready)
   await router.isReady();
-
   // 挂载应用
   app.mount('#app', true);
 }

+ 33 - 12
src/utils/index.ts

@@ -72,10 +72,10 @@ export function getDynamicProps<T, U>(props: T): Partial<U> {
  * @updateBy:zyf
  */
 export function getValueType(props, field) {
-  let formSchema = unref(unref(props)?.schemas);
+  const formSchema = unref(unref(props)?.schemas);
   let valueType = 'string';
   if (formSchema) {
-    let schema = formSchema.filter((item) => item.field === field)[0];
+    const schema = formSchema.filter((item) => item.field === field)[0];
     valueType = schema.componentProps && schema.componentProps.valueType ? schema.componentProps.valueType : valueType;
   }
   return valueType;
@@ -119,11 +119,11 @@ export const withInstall = <T>(component: T, alias?: string) => {
  * @param paraName
  */
 export function getUrlParam(paraName) {
-  let url = document.location.toString();
-  let arrObj = url.split('?');
+  const url = document.location.toString();
+  const arrObj = url.split('?');
 
   if (arrObj.length > 1) {
-    let arrPara = arrObj[1].split('&');
+    const arrPara = arrObj[1].split('&');
     let arr;
 
     for (let i = 0; i < arrPara.length; i++) {
@@ -162,7 +162,7 @@ export function sleep(ms: number, fn?: Fn) {
  * @returns {String} 替换后的字符串
  */
 export function replaceAll(text, checker, replacer) {
-  let lastText = text;
+  const lastText = text;
   text = text.replace(checker, replacer);
   if (lastText !== text) {
     return replaceAll(text, checker, replacer);
@@ -177,14 +177,14 @@ export function replaceAll(text, checker, replacer) {
 export function getQueryVariable(url) {
   if (!url) return;
 
-  var t,
+  let t,
     n,
     r,
     i = url.split('?')[1],
     s = {};
   (t = i.split('&')), (r = null), (n = null);
-  for (var o in t) {
-    var u = t[o].indexOf('=');
+  for (const o in t) {
+    const u = t[o].indexOf('=');
     u !== -1 && ((r = t[o].substr(0, u)), (n = t[o].substr(u + 1)), (s[r] = n));
   }
   return s;
@@ -207,7 +207,7 @@ export function showDealBtn(bpmStatus) {
  */
 export function numToUpper(value) {
   if (value != '') {
-    let unit = new Array('仟', '佰', '拾', '', '仟', '佰', '拾', '', '角', '分');
+    const unit = ['仟', '佰', '拾', '', '仟', '佰', '拾', '', '角', '分'];
     const toDx = (n) => {
       switch (n) {
         case '0':
@@ -232,10 +232,10 @@ export function numToUpper(value) {
           return '玖';
       }
     };
-    let lth = value.toString().length;
+    const lth = value.toString().length;
     value *= 100;
     value += '';
-    let length = value.length;
+    const length = value.length;
     if (lth <= 8) {
       let result = '';
       for (let i = 0; i < length; i++) {
@@ -311,3 +311,24 @@ export function goJmReportViewPage(url, id, token) {
   url += `&token=${token}`;
   window.open(url);
 }
+
+// 防抖截流
+export function debounce(delay, callback) {
+  let task;
+  return function () {
+    clearTimeout(task);
+    task = setTimeout(() => {
+      callback.apply(this, arguments);
+    }, delay);
+  };
+}
+
+export function setRem() {
+  // 默认使用100px作为基准大小
+  const baseSize = 100;
+  const baseVal = baseSize / 1920;
+  const vW = window.innerWidth; // 当前窗口的宽度
+  const rem = vW * baseVal; // 以默认比例值乘以当前窗口宽度,得到该宽度下的相应font-size值
+  window.$size = rem / 100;
+  document.documentElement.style.fontSize = rem + 'px';
+}

+ 109 - 0
src/utils/threejs/FlyLine1.ts

@@ -0,0 +1,109 @@
+import * as THREE from 'three';
+
+class Fly {
+  points
+  length
+  circle
+  opacity
+  size
+  progress
+  frameId
+  geometry
+  material
+  texture
+  obj
+  color
+  constructor(points, length, circle = 2, color = "#ff00ff", opacity = 1, size = 10) {
+    this.points = points; // 路径
+    this.length = length; // 长度(粒子数)
+    this.circle = circle; // 周期
+    this.color = color; // 颜色
+    this.opacity = opacity; // 透明度
+    this.size = size; // 大小
+    this.progress = 0;
+    this.frameId = null;
+    this.geometry = null;
+    this.material = null;
+    this.texture = null;
+    this.obj = null;
+
+    this.createFly();
+  }
+
+  createFly() {
+    // 几何体
+    this.geometry = new THREE.BufferGeometry();
+    this.updateFly();
+
+    // 纹理和材质
+    this.texture = new THREE.TextureLoader().load("http://182.43.179.137:81/public/images/texture-circle-gradient1.png");
+    this.material = new THREE.PointsMaterial({
+      color: this.color,
+      map: this.texture,
+      // alphaTest: 0.9,
+      transparent: true,
+      depthWrite: false,
+      opacity: this.opacity,
+      //blending: THREE.AdditiveBlending,
+      size: this.size,
+      sizeAttenuation: true,
+    });
+
+    // 修正着色器
+    this.material.onBeforeCompile = (shader) => {
+      const vertex = `
+              attribute float aScale;
+              void main() {
+            `;
+      const vertex1 = `gl_PointSize = size * aScale;`;
+      shader.vertexShader = shader.vertexShader.replace("void main() {", vertex);
+      shader.vertexShader = shader.vertexShader.replace("gl_PointSize = size;", vertex1);
+    };
+
+    // 物体
+    this.obj = new THREE.Points(this.geometry, this.material);
+  }
+
+  // 更新
+  updateFly() {
+    // 计算新数据
+    const posArr = [];
+    const scaleArr = [];
+    const posIndex = Math.floor(this.progress * this.points.length);
+    const flyPointArr = this.points.filter((point, index) => {
+      if (index >= posIndex && index <= posIndex + this.length) return true;
+    });
+    flyPointArr.forEach((point, index) => {
+      posArr.push(...point);
+      scaleArr.push((index + 1) / this.length);
+    });
+    // 更新几何体
+    this.geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(posArr), 3));
+    this.geometry.setAttribute("aScale", new THREE.BufferAttribute(new Float32Array(scaleArr), 1));
+  }
+
+  // 移动
+  move() {
+    if (this.frameId) return;
+    const clock = new THREE.Clock(); // 时钟
+    const h = () => {
+      this.frameId = requestAnimationFrame(h);
+      const dt = clock.getDelta();
+      this.progress += dt / this.circle; // 更新进度
+      if (this.progress > 1) this.progress = 1;
+      this.updateFly();
+      if (this.progress == 1) this.progress = 0;
+    };
+    this.frameId = requestAnimationFrame(h);
+  }
+
+  // 停止
+  stop() {
+    if (this.frameId) {
+      cancelAnimationFrame(this.frameId);
+      this.frameId = null;
+    }
+  }
+}
+export default Fly
+

+ 1 - 1
src/utils/threejs/ResourceTracker.js

@@ -45,7 +45,7 @@ export default class ResourceTracker {
     return resource;
   }
   untrack(resource) {
-    resource = null;
+    resource = undefined;
     this.resources.delete(resource);
   }
   dispose() {

+ 1 - 1
src/utils/threejs/main.worker.ts

@@ -8,7 +8,7 @@ export function initModalWorker() {
     modelVal: any;
   };
 
-  const modalUrlArr = ['fm/fm-5.glb', 'fc/fc.glb'];
+  const modalUrlArr = ['9f/9f-processed.glb', 'fc/sdFc.glb', 'fc/ddFc.glb', 'cf/lmcf.glb', 'cf/zdcf.glb', 'jbfj/jbfj_hd.glb', 'jbfj/jbfj_fm.glb', 'jbfj/jbfj_fc.glb'];
 
   const db: any = new Dexie('DB');
   window['CustomDB'] = db;

+ 90 - 1
src/utils/threejs/util.ts

@@ -6,6 +6,7 @@ import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
 import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';
 import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
 import gsap from 'gsap';
+import { onUpdated } from 'vue';
 // import * as dat from "dat.gui";
 
 let modal,
@@ -205,7 +206,6 @@ export const setLineGeo = (scene) => {
     scene.add(line);
   });
 
-  debugger;
   box.attributes.position.array;
 
   const lightMaterial = new THREE.ShaderMaterial({
@@ -354,3 +354,92 @@ export const setOutline = (model, group) => {
   group.scale.divideScalar(scale);
   return { outlinePass, composer };
 };
+
+// 视频
+
+/* 渲染视频 */
+export const renderVideo = (group, player, playerMeshName) => {
+  //加载视频贴图;
+  const texture = new THREE.VideoTexture(player);
+  if (texture && group.getObjectByName(playerMeshName)) {
+    const player = group.getObjectByName(playerMeshName);
+    player.material.map = texture;
+  } else {
+    //创建网格;
+    const planeGeometry = new THREE.PlaneGeometry(30, 20);
+    const material = new THREE.MeshBasicMaterial({
+      map: texture,
+      side: THREE.DoubleSide,
+    });
+    /* 消除摩尔纹 */
+    texture.magFilter = THREE.LinearFilter;
+    texture.minFilter = THREE.LinearFilter;
+    texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;
+    texture.format = THREE.RGBAFormat;
+    texture.anisotropy = 0.5;
+    // texture.generateMipmaps = false
+
+    const mesh = new THREE.Mesh(planeGeometry, material);
+    mesh.name = playerMeshName;
+
+    group.add(mesh);
+    return mesh;
+  }
+};
+
+// oldP  相机原来的位置
+// oldT  target原来的位置
+// newP  相机新的位置
+// newT  target新的位置
+// callBack  动画结束时的回调函数
+export const animateCamera = (oldP, oldT, newP, newT, model) => {
+  const camera = model.camera;
+  const controls = model.orbitControls;
+  controls.enabled = false;
+  controls.target.set(0, 0, 0);
+  const animateObj = {
+    x1: oldP.x, // 相机x
+    y1: oldP.y, // 相机y
+    z1: oldP.z, // 相机z
+    x2: oldT.x, // 控制点的中心点x
+    y2: oldT.y, // 控制点的中心点y
+    z2: oldT.z, // 控制点的中心点z
+  };
+  gsap.fromTo(
+    animateObj,
+    {
+      x1: oldP.x, // 相机x
+      y1: oldP.y, // 相机y
+      z1: oldP.z, // 相机z
+      x2: oldT.x, // 控制点的中心点x
+      y2: oldT.y, // 控制点的中心点y
+      z2: oldT.z, // 控制点的中心点z
+    },
+    {
+      x1: newP.x,
+      y1: newP.y,
+      z1: newP.z,
+      x2: newT.x,
+      y2: newT.y,
+      z2: newT.z,
+      duration: 0.5,
+      ease: 'easeOutBounce',
+      onUpdate: function (object) {
+        // 这里写逻辑
+        camera.position.x = object.x1;
+        camera.position.y = object.y1;
+        camera.position.z = object.z1;
+        // controls.target.x = object.x2;
+        // controls.target.y = object.y2;
+        // controls.target.z = object.z2;
+        controls.update();
+      },
+      onUpdateParams: [animateObj],
+      onComplete: function () {
+        // 完成
+        controls.enabled = true;
+        console.log(controls.target);
+      },
+    }
+  );
+};

+ 1 - 1
src/views/demo/threejs/damper.vue

@@ -7,7 +7,7 @@
 
 <script setup lang="ts">
   import { ref, onMounted, onUnmounted } from 'vue';
-  import UseThree from '/@/hooks/core/useThree';
+  import UseThree from '../../../hooks/core/threejs/useThree';
   import * as THREE from 'three';
   import gsap from 'gsap';
 

+ 1 - 1
src/views/vent/comment/EditRowTable.vue

@@ -136,7 +136,7 @@
         if (column.dataIndex === 'id') {
           record.editValueRefs.name4.value = `${value}`;
         }
-        console.log(column, value, record);
+        // console.log(column, value, record);
       }
 
       return {

+ 2 - 2
src/views/vent/deviceManager/windWindowTabel/ventanalyWindow.data.ts

@@ -133,8 +133,8 @@ export const formSchema: FormSchema[] = [
     component: 'InputNumber',
   },
   {
-    label: '风道数',
-    field: 'ndoorcount',
+    label: '风道数',
+    field: 'nwindownum',
     component: 'InputNumber',
   },
   {

+ 43 - 31
src/views/vent/monitorManager/comment/MonitorTable.vue

@@ -7,9 +7,6 @@
       <template #tableTop>
         <div></div>
       </template>
-      <template #action="{ record }">
-        <TableAction :actions="getActions(record)" :dropDownActions="getDropDownAction(record)" />
-      </template>
     </BasicTable>
   </div>
 </template>
@@ -17,11 +14,13 @@
 <script lang="ts" name="system-user" setup>
   //ts语法
   import { computed } from '@vue/reactivity';
-  import { defineExpose, toRaw, watch, ref } from 'vue';
+  import { defineExpose, toRaw, watch, ref, onMounted, onUnmounted } from "vue";
   import { BasicTable, TableAction } from '/@/components/Table';
   import { useListPage } from '/@/hooks/system/useListPage';
   import { getTableHeaderColumns, setWebColumnsKey } from '/@/hooks/web/useWebColumns';
   import { findIndex } from 'lodash-es';
+  import { defHttp } from '/@/utils/http/axios';
+  const listApi = '/ventanaly-device/monitor/device'
   const props = defineProps({
     columnsType: {
       type: String,
@@ -31,13 +30,8 @@
       type: Array,
       required: true,
     },
-    searchFormSchema: {
-      type: Array,
-      default: () => [],
-    },
-    list: {
-      type: Function,
-      // required: true,
+    deviceType: {
+      type: String,
     },
     designScope: {
       type: String,
@@ -49,7 +43,11 @@
   const emits = defineEmits(['selectRow']);
   const dataTableSource = ref([]);
   const loading = ref(true);
+
+  // 默认初始是第一行
+  const selectRowIndex = ref(0);
   const tableMaxHeight = 150;
+
   watch(
     () => {
       return props.dataSource;
@@ -67,6 +65,28 @@
       loading.value = false;
     }
   );
+
+  let timer: null | NodeJS.Timeout = null;
+  const getMonitor = (callBackFn:Function) => {
+    const callBack = callBackFn
+    if (Object.prototype.toString.call(timer) === '[object Null]') {
+      timer = setTimeout(() => {
+        defHttp.post({ url: listApi, params: { devicetype: props.deviceType, pagetype: 'normal' } }).then((res) => {
+          dataTableSource.value = res.msgTxt[0].datalist || [];
+          dataTableSource.value.forEach((data: any) => {
+            const readData = data.readData;
+            data = Object.assign(data, readData);
+          });
+          const data: any = toRaw(dataTableSource.value[selectRowIndex.value])
+          callBackFn()
+          timer = null;
+          getMonitor(callBack);
+        });
+      }, 1000);
+    }
+  };
+
+
   setWebColumnsKey(props.columnsType);
   const columns = computed(() => getTableHeaderColumns);
   // 列表页面公共参数、方法
@@ -99,8 +119,6 @@
     const index = findIndex(dataTableSource.value, (data: any) => {
       return data.deviceID == record.deviceID;
     });
-    console.log('选中行', index);
-
     emits('selectRow', record, index);
   };
 
@@ -133,27 +151,21 @@
       },
     ];
   }
-  /**
-   * 下拉操作栏
-   */
-  function getDropDownAction(record) {
-    return [
-      // {
-      //   label: '删除',
-      //   popConfirm: {
-      //     title: '是否确认删除',
-      //     confirm: handleDelete.bind(null, record),
-      //   },
-      // },
-      // {
-      //   label: '查看',
-      //   onClick: handleDetail.bind(null, record),
-      // },
-    ];
-  }
+
   defineExpose({
     doRequest,
   });
+
+  onMounted(() => {
+    // 如果是https
+
+    // 反之是websocket
+
+  })
+
+  onUnmounted(() => {
+    timer = undefined;
+  });
 </script>
 
 <style scoped lang="less">

+ 3 - 4
src/views/vent/monitorManager/comment/MonitorTable1.vue

@@ -16,8 +16,8 @@
 <script lang="ts" name="system-user" setup>
   //ts语法
   import { computed } from '@vue/reactivity';
-import { debug } from 'console';
-import { defineExpose, toRaw, watch, ref } from 'vue';
+  import { debug } from 'console';
+  import { defineExpose, toRaw, watch, ref } from 'vue';
   import { BasicColumn } from '/@/components/Table';
   import { getTableHeaderColumns, setWebColumnsKey } from '/@/hooks/web/useWebColumns';
 
@@ -82,7 +82,6 @@ import { defineExpose, toRaw, watch, ref } from 'vue';
     // }),
   };
 
-
   function openDetail(record) {
     record;
   }
@@ -92,7 +91,7 @@ import { defineExpose, toRaw, watch, ref } from 'vue';
   :deep(.ant-table-body) {
     height: auto !important;
   }
-  .monitor-table{
+  .monitor-table {
     width: 100%;
   }
 </style>

+ 298 - 0
src/views/vent/monitorManager/fanLocalMonitor/fanLocal.three.ts

@@ -0,0 +1,298 @@
+import * as THREE from 'three';
+import { animateCamera, getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+import UseThree from '../../../../hooks/core/threejs/useThree';
+import Fly from '/@/utils/threejs/FlyLine1';
+import fcFan from './fcfanLocal.three';
+import fmFan from './fmfanLocal.three';
+
+const modelName = 'jbfj_hd';
+// 模型对象、 文字对象
+let model, group, fcFanObj, fmFanObj, player1, fanType;
+
+// 打灯光
+const addLight = (scene) => {
+  // const pointLight2 = new THREE.PointLight(0xffeeee, 1.5, 100);
+  // pointLight2.position.set(-120, 16, -33);
+  // pointLight2.shadow.bias = 0.05;
+  // scene.add(pointLight2);
+  //
+  // const pointLight3 = new THREE.PointLight(0xffffff, 1, 40);
+  // pointLight3.position.set(-66, 40, 1);
+  // pointLight3.shadow.bias = 0.05;
+  // scene.add(pointLight3);
+  //
+  // const pointLight4 = new THREE.PointLight(0xffeeee, 0.6, 230);
+  // pointLight4.position.set(-18, 30, 12);
+  // pointLight4.shadow.bias = 0.05;
+  // scene.add(pointLight4);
+  //
+  // const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 90);
+  // pointLight5.position.set(-57, 7, -30);
+  // pointLight5.shadow.bias = 0.05;
+  // scene.add(pointLight5);
+  //
+  // const pointLight6 = new THREE.PointLight(0xffffff, 0.8, 270);
+  // pointLight6.position.set(72, -33, 11.4);
+  // pointLight6.shadow.bias = 0.05;
+  // scene.add(pointLight6);
+
+  const pointLight7 = new THREE.PointLight(0xffffff, 0.8, 500);
+  pointLight7.position.set(-20, -43, 12);
+  pointLight7.shadow.bias = -0.05;
+  scene.add(pointLight7);
+
+  const spotLight = new THREE.SpotLight();
+  spotLight.angle = Math.PI / 16;
+  spotLight.penumbra = 0;
+  spotLight.castShadow = true;
+  spotLight.position.set(0, 463, 687);
+  scene.add(spotLight);
+
+  spotLight.shadow.camera.near = 0.5; // default
+  spotLight.shadow.camera.far = 1000; // default
+  spotLight.shadow.focus = 1;
+  spotLight.shadow.bias = -0.000002;
+
+  // gui.add(pointLight6.position, 'x', -200, 200);
+  // gui.add(pointLight6.position, 'y', -200, 200);
+  // gui.add(pointLight6.position, 'z', -200, 200);
+  // gui.add(pointLight6, 'distance', 0, 500);
+};
+
+// 重置摄像头
+const resetCamera = () => {
+  model.camera.position.set(0, 30, 80);
+  model.orbitControls?.update();
+  model.camera.updateProjectionMatrix();
+};
+
+// 设置模型位置
+const setModalPosition = () => {
+  group.position.set(0, 18, -50);
+  group.rotation.y = Math.PI / 2;
+};
+
+// 切换局部通风机类型
+export const setModelType = (type) => {
+  fanType = type;
+  model.camera.position.set(500, 500, 500);
+
+  return new Promise((resolve) => {
+    // 显示双道风窗
+    if (fanType === 'fm') {
+      if (group.getObjectByName('jbfj_fc')) {
+        group.remove(fcFanObj.group);
+      }
+      setTimeout(() => {
+        resolve(null);
+
+        const position = { x: 0, y: 0, z: 0 };
+        animateCamera({ x: 500, y: 500, z: 500 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 30, z: 80 }, { x: position.x, y: position.y, z: position.z }, model);
+        group.add(fmFanObj.group);
+      }, 300);
+    } else if (fanType === 'fc') {
+      // 显示单道风窗
+      if (group.getObjectByName('jbfj_fm')) {
+        group.remove(fmFanObj.group);
+      }
+      setTimeout(() => {
+        resolve(null);
+        const position = { x: 0, y: 0, z: 0 };
+        animateCamera({ x: 500, y: 500, z: 500 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 30, z: 80 }, { x: position.x, y: position.y, z: position.z }, model);
+        group.add(fcFanObj.group);
+      }, 300);
+    }
+  });
+};
+
+const setControls = () => {
+  model.orbitControls.panSpeed = 0.5;
+  model.orbitControls.rotateSpeed = 0.5;
+  model.orbitControls.maxPolarAngle = Math.PI / 2.4;
+  model.orbitControls.minPolarAngle = Math.PI / 3;
+};
+
+export const addFmText = (selectData) => {
+  if (!group) {
+    return;
+  }
+  const textArr = [
+    {
+      text: `煤矿巷道远程风门系统`,
+      font: 'normal 2.2rem Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 80,
+      y: 95,
+    },
+    {
+      text: `压力(Pa):`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 0,
+      y: 155,
+    },
+    {
+      text: `${selectData.frontRearDP}`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 290,
+      y: 155,
+    },
+    {
+      text: `动力源压力(MPa): `,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 0,
+      y: 215,
+    },
+    {
+      text: ` ${selectData.sourcePressure}`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 280,
+      y: 215,
+    },
+    {
+      text: `故障诊断:`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 0,
+      y: 275,
+    },
+    {
+      text: `${selectData.fault}`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 280,
+      y: 275,
+    },
+    {
+      text: `煤炭科学技术研究院有限公司研制`,
+      font: 'normal 28px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 20,
+      y: 325,
+    },
+  ];
+
+  getTextCanvas(526, 346, textArr, '').then((canvas) => {
+    const textMap = track(new THREE.CanvasTexture(canvas)); // 关键一步
+    const textMaterial = track(
+      new THREE.MeshBasicMaterial({
+        // 关于材质并未讲解 实操即可熟悉                 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
+        map: textMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.FrontSide, // 这里是双面渲染的意思
+      })
+    );
+    textMaterial.blending = THREE.CustomBlending;
+    const monitorPlane = group.getObjectByName('monitorText');
+    if (monitorPlane) {
+      monitorPlane.material = textMaterial;
+    } else {
+      const planeGeometry = track(new THREE.PlaneGeometry(526, 346)); // 平面3维几何体PlaneGeometry
+      const planeMesh = track(new THREE.Mesh(planeGeometry, textMaterial));
+      planeMesh.name = 'monitorText';
+      planeMesh.scale.set(0.002, 0.002, 0.002);
+      planeMesh.position.set(-1.255, 0.09, -0.41);
+      group.add(planeMesh);
+    }
+  });
+};
+
+const setFly = () => {
+  // 路径
+  const points = [];
+  const curve = new THREE.CatmullRomCurve3([
+    new THREE.Vector3(-400, 10, 0),
+    new THREE.Vector3(-50, 10, 150),
+    new THREE.Vector3(50, 10, -50),
+    new THREE.Vector3(300, 10, 150),
+    new THREE.Vector3(350, 10, 150),
+    new THREE.Vector3(500, 150, 0),
+    new THREE.Vector3(600, 0, 0),
+  ]);
+  // 采样
+  curve.getSpacedPoints(500).forEach((point) => {
+    const { x, y, z } = point;
+    points.push([x, y, z]);
+  });
+  const length = 50;
+  const circle = 10;
+  const color = '#ee0000';
+  const opacity = 1;
+  const size = 15;
+  const fly = new Fly(points, length, circle, color, opacity, size);
+  fly.move();
+  model.scene.add(fly.obj);
+
+  // 路径轨迹
+  const material = new THREE.LineBasicMaterial({
+    color: '#FFff00',
+  });
+  const geometry = new THREE.BufferGeometry().setFromPoints(curve.getSpacedPoints(5000));
+  const line = new THREE.Line(geometry, material);
+  model.scene.add(line);
+};
+
+export const mountedThree = (playerVal1) => {
+  player1 = playerVal1;
+  return new Promise((resolve) => {
+    model = new UseThree('#fanLocal3D');
+    model.setEnvMap('test1');
+    model.renderer.toneMappingExposure = 0.8;
+
+    model.setModel(modelName).then(async (gltf) => {
+      group = gltf.scene;
+      model.scene?.add(group);
+      if (gltf.animations && gltf.animations.length > 0) {
+        model.mixers = [];
+        model.animations = [];
+        gltf.animations.forEach((animation) => {
+          const mixer = new THREE.AnimationMixer(group);
+          model.mixers.push(mixer);
+          model.animations.push(animation);
+        });
+      }
+      addLight(model.scene);
+      resetCamera();
+      setModalPosition();
+      setControls();
+
+      fcFanObj = new fcFan(model);
+      await fcFanObj.mountedThree();
+
+      fmFanObj = new fmFan(model);
+      await fmFanObj.mountedThree();
+
+      model.animate();
+      setFly();
+      debugger
+      const videoPlayer1 = document.getElementById('jb-player1')?.getElementsByClassName('vjs-tech')[0];
+      if (videoPlayer1) {
+        const mesh = renderVideo(group, videoPlayer1, 'player1');
+        mesh.scale.set(0.222, 0.190, 0.200);
+        mesh.position.set(-84.870, 0.298, 24.760);
+        mesh.rotation.y = -Math.PI/2
+        group.add(mesh)
+      }
+      resolve(model);
+    });
+  });
+};
+
+export const destroy = () => {
+  if (model) {
+    model.deleteModal();
+    model = null;
+    group = null;
+  }
+};

+ 27 - 0
src/views/vent/monitorManager/fanLocalMonitor/fcfanLocal.three.ts

@@ -0,0 +1,27 @@
+import * as THREE from 'three';
+
+class fcFan {
+  model;
+  modelName = 'jbfj_fc';
+  group = null;
+
+  constructor(model) {
+    this.model = model;
+  }
+
+  mountedThree() {
+    return new Promise((resolve) => {
+      this.model.setModel(this.modelName).then((gltf) => {
+        this.group = gltf.scene;
+        this.group.position.set(-0.056, -0.007, 3.494)
+        resolve(null);
+      });
+    });
+  }
+
+  destroy() {
+    this.model = null;
+    this.group = null;
+  }
+}
+export default fcFan;

+ 26 - 0
src/views/vent/monitorManager/fanLocalMonitor/fmfanLocal.three.ts

@@ -0,0 +1,26 @@
+import * as THREE from 'three';
+
+class fmFan {
+  model;
+  modelName = 'jbfj_fm';
+  group = null;
+
+  constructor(model) {
+    this.model = model;
+  }
+  mountedThree() {
+    return new Promise((resolve) => {
+      this.model.setModel(this.modelName).then((gltf) => {
+        this.group = gltf.scene;
+        this.group.position.set(-0.668, -0.037, -0.023)
+        resolve(null);
+      });
+    });
+  }
+
+  destroy() {
+    this.model = null;
+    this.group = null;
+  }
+}
+export default fmFan;

+ 273 - 9
src/views/vent/monitorManager/fanLocalMonitor/index.vue

@@ -1,18 +1,282 @@
 <template>
-  <MonitorTable columnsType="fanlocal_monitor" :dataSource="dataSource" design-scope="fanlocal-monitor" title="局部通风机监测" />
+  <div class="bg" style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden">
+    <a-spin :spinning="loading" />
+    <div id="fanLocal3D" v-show="!loading" style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
+  </div>
+  <div class="scene-box">
+    <div class="top-box">
+      <div class="top-left row"> 井下测风装置集中管理 </div>
+      <div class="top-center row">
+        <!--        <div class="button-box" @click="play('up')">上</div>-->
+        <!--        <div class="button-box" @click="play('center')">中</div>-->
+        <!--        <div class="button-box" @click="play('down')">下</div>-->
+      </div>
+      <div class="top-right row">
+        <div class="control-type row">
+          <div class="control-title">控制模式:</div>
+          <a-radio-group v-model:value="controlType">
+            <a-radio :value="1">就地</a-radio>
+            <a-radio :value="2">远程</a-radio>
+          </a-radio-group>
+        </div>
+        <div class="run-type row">
+          <div class="control-title">运行状态:</div>
+          <a-radio-group v-model:value="controlType">
+            <a-radio :value="1">检修</a-radio>
+          </a-radio-group>
+        </div>
+        <div class="run-state row">
+          <div class="control-title">网络状态:</div>
+          <a-radio-group v-model:value="controlType">
+            <a-radio :value="1">运行</a-radio>
+          </a-radio-group>
+        </div>
+      </div>
+    </div>
+    <div class="title-box"> 2-2煤主辅三联巷局部通风 </div>
+    <div class="tabs-box">
+      <a-tabs v-model:activeKey="activeKey" @change="tabChange">
+        <a-tab-pane key="1" tab="实时监测">
+          <MonitorTable columnsType="fanlocal_monitor" :dataSource="dataSource" design-scope="fanlocal-monitor" title="局部通风机监测" />
+        </a-tab-pane>
+        <a-tab-pane key="2" tab="实时曲线图" force-render>
+          <div class="tab-item" v-if="activeKey === '2'">
+            <Bar :chartData="dataSource" xAxisPropType="strname" :propTypeArr="propTypeArr" height="40vh" width="100%" />
+          </div>
+        </a-tab-pane>
+        <a-tab-pane key="3" tab="历史数据">
+          <div class="tab-item"> Content of Tab Pane 2 </div>
+        </a-tab-pane>
+        <a-tab-pane key="4" tab="操作历史">
+          <div class="tab-item"> Content of Tab Pane 2 </div>
+        </a-tab-pane>
+        <a-tab-pane key="5" tab="实时报警">
+          <div class="tab-item"> Content of Tab Pane 2 </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
+  </div>
+  <div style="z-index: -1; position: absolute; top: 50px; right: 10px; width:300px;height:280px;margin:auto" class="palyer1">
+    <LivePlayer id="jb-player1" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
+  </div>
 </template>
 
 <script setup lang="ts">
-  import { onBeforeMount, computed } from 'vue';
-  // import Bar from '/@/components/chart/Bar.vue';
+  import '/@/assets/less/modal.less';
+  import {
+    onBeforeMount,
+    computed,
+    ref,
+    onMounted,
+    nextTick,
+    toRaw,
+    reactive,
+    onUnmounted
+  } from "vue";
+  import Bar from '/@/components/chart/Bar.vue';
   import MonitorTable from '../comment/MonitorTable.vue';
   import { initWebSocket, getRecordList } from '/@/hooks/web/useVentWebSocket';
-  const dataSource = computed(() => {
-    return [...getRecordList()].reverse() || [];
-  });
+  import { mountedThree, setModelType, destroy } from './fanLocal.three';
+  import lodash from "lodash";
+  import { getTableList, list } from "/@/views/vent/monitorManager/windowMonitor/window.api";
+  import LivePlayer from '@liveqing/liveplayer-v3';
+
+  const loading = ref(false);
+  const activeKey = ref('1');
+  const player1 = ref()
+  // 默认初始是第一行
+  const selectRowIndex = ref(0);
+  // 设备数据
+  const controlType = ref(1);
+  // 监测数据
+  const initData = {
+    deviceID: '',
+    deviceType: '',
+    strname: '',
+    dataDh: '-', //压差
+    dataDtestq: '-', //测试风量
+    sourcePressure: '-', //气源压力
+    dataDequivalarea: '-',
+    netStatus: '0', //通信状态
+    fault: '气源压力超限',
+  };
+  // 监测数据
+  const selectData = reactive(lodash.cloneDeep(initData));
+  // const dataSource = computed(() => {
+  //   const data = [...getRecordList()] || [];
+  //   Object.assign(selectData, toRaw(data[selectRowIndex.value]));
+  //   return data;
+  // });
+  const dataSource = ref([]);
+
+  const getDataSource = async() => {
+    const res = await list({ devicetype: 'fan', pagetype: 'normal' })
+    dataSource.value = res.msgTxt[0].datalist || [];
+    dataSource.value.forEach((data: any) => {
+      const readData = data.readData;
+      data = Object.assign(data, readData);
+    });
+    const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
+    return data
+  }
+
+  // https获取监测数据
+  let timer: null | NodeJS.Timeout = null;
+  const getMonitor = () => {
+    if (Object.prototype.toString.call(timer) === '[object Null]') {
+      timer = setTimeout(async () => {
+        const data = await getDataSource()
+        Object.assign(selectData, data);
+        // playAnimation(data, selectData.maxarea);
+        // addFmText(selectData);
+        if(timer){
+          timer = null;
+        }
+        getMonitor();
+      }, 1000);
+    }
+  };
+
+  // 获取设备基本信息列表
+  const deviceBaseList = ref([]);
+  const getDeviceBaseList = () => {
+    getTableList({ pageSize: 1000 }).then((res) => {
+      deviceBaseList.value = res.records;
+    });
+  };
+  // 切换检测数据
+  const getSelectRow = (selectRow, index) => {
+    selectRowIndex.value = index;
+    loading.value = true;
+    const baseData: any = deviceBaseList.value.find((baseData: any) => baseData.id === selectRow.deviceID);
+    Object.assign(selectData, initData, selectRow, baseData);
+
+    const type = selectRowIndex.value > 6 ? 'fm': 'fc'
+    setModelType(type).then(() => {
+      // addFmText(selectData);
+      // playAnimation(selectRow, baseData.maxarea, true);
+      loading.value = false;
+    })
+  };
+
+  const tabChange = (activeKeyVal) => {
+    activeKey.value = activeKeyVal;
+  };
+  const flvURL1 = () =>{
+    return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`
+  }
+  const addPlayVideo = () => {
+    if (player1.value.play) {
+      if(!player1.value.paused())  player1.value.play();
+      document.body.removeEventListener('mousedown', addPlayVideo);
+    }
+  };
   onBeforeMount(() => {
-    const sendVal = JSON.stringify({ pagetype: 'normal', devicetype: 'fan', orgcode: '', ids: '', systemID: '' });
-    initWebSocket(sendVal);
+    // const sendVal = JSON.stringify({ pagetype: 'normal', devicetype: 'fan', orgcode: '', ids: '', systemID: '' });
+    // initWebSocket(sendVal);
+    getDeviceBaseList();
+    document.body.addEventListener('mousedown', addPlayVideo, false);
+  });
+  onMounted(() => {
+    loading.value = true;
+    mountedThree(player1).then(() => {
+      nextTick(() => {
+        setModelType('fm')
+        loading.value = false;
+        getMonitor();
+        // addFmText(selectData);
+      });
+    });
+  });
+  onUnmounted(() => {
+    destroy();
+    if(timer) {
+      clearTimeout(timer)
+      timer = undefined;
+    }
   });
 </script>
-<style scoped lang="scss"></style>
+<style scoped lang="less">
+  .input-box {
+    display: flex;
+    align-items: center;
+    .input-title {
+      color: rgb(0, 255, 242);
+      width: auto;
+    }
+    margin-right: 10px;
+  }
+
+  :deep(.jeecg-basic-table .ant-table-wrapper) {
+    background-color: #ffffff00;
+  }
+  :deep(.ant-tabs-bar) {
+    margin: 0;
+  }
+  :deep(.ant-table) {
+    background-color: #ffffff00 !important;
+    color: #fff;
+  }
+  :deep(.ant-table-header) {
+    background-color: transparent;
+    // height: 42px;
+  }
+  :deep(.ant-table-thead > tr > th) {
+    background-color: transparent;
+    border: none;
+  }
+  :deep(.ant-table-body > tr > th) {
+    background-color: transparent;
+    border: none;
+  }
+  :deep(.ant-table-body > tr > td) {
+    border: none;
+  }
+  :deep(.ant-table-fixed-header > .ant-table-content > .ant-table-scroll > .ant-table-body) {
+    background-color: #ffffff05;
+    margin-top: 8px;
+    &::-webkit-scrollbar {
+      display: none;
+    }
+  }
+
+  :deep(.jeecg-basic-table .ant-table-wrapper .ant-table-title) {
+    padding: 0;
+  }
+  :deep(.jeecg-basic-table-row__striped td) {
+    background-color: transparent;
+  }
+  :deep(.ant-table-tbody > tr:hover.ant-table-row > td) {
+    background-color: #ffffff22;
+  }
+  :deep(.ant-table-tbody > tr:hover.ant-table-row > th) {
+    background-color: #ffffff22;
+  }
+  :deep(.ant-table-thead > tr:hover.ant-table-row > td) {
+    background-color: #ffffff22;
+  }
+  :deep(.ant-table-tbody > tr.ant-table-row-selected td) {
+    background-color: #ffffff22;
+  }
+  :deep(.ant-table-tbody > tr > td) {
+    border-color: #ffffff22;
+  }
+  :deep(.ant-table-thead > tr > th:hover) {
+    background-color: transparent !important;
+  }
+  :deep(.ant-table-thead > tr > th) {
+    color: #fff;
+  }
+  :deep(.ant-table-fixed-header .ant-table-scroll .ant-table-header) {
+    background: #ffffff44;
+    position: relative;
+    z-index: 999;
+    padding: 4px 0 !important;
+    &::-webkit-scrollbar {
+      display: none;
+    }
+  }
+  :deep(.ant-tabs-nav) {
+    color: #fff;
+  }
+</style>

+ 1 - 1
src/views/vent/monitorManager/gateMonitor/detail.vue

@@ -7,7 +7,7 @@
 
 <script setup lang="ts">
   import { ref, onMounted, onUnmounted } from 'vue';
-  import UseThree from '/@/hooks/core/useThree';
+  import UseThree from '../../../../hooks/core/threejs/useThree';
   import * as THREE from 'three';
   import gsap from 'gsap';
 

+ 595 - 0
src/views/vent/monitorManager/gateMonitor/gate.threejs.ts

@@ -0,0 +1,595 @@
+import * as THREE from 'three';
+
+import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
+import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+import UseThree from '../../../../hooks/core/threejs/useThree';
+
+import { flyLine } from '/@/utils/threejs/FlyLine';
+import { createComposer } from '/@/utils/threejs/bloomPass';
+
+const modelName = 'fm';
+// 模型对象、 文字对象
+let model, //
+  track,
+  group,
+  fmCSS3D, //文字
+  isLRAnimation = true, // 是否开启左右摇摆动画
+  direction = 1, // 摇摆方向
+  animationtimer: NodeJS.Timeout | null, // 摇摆开启定时器
+  renderBloomPass,
+  player1,
+  player2,
+  playerStartClickTime1 = new Date().getTime(),
+  playerStartClickTime2 = new Date().getTime();
+
+const clipActionArr = {
+  frontDoor: null as unknown as THREE.AnimationAction,
+  backDoor: null as unknown as THREE.AnimationAction,
+  arrowTracks: null as unknown as THREE.AnimationAction,
+};
+
+// 打灯光
+const addLight = (scene) => {
+  const pointLight2 = new THREE.PointLight(0xffeeee, 0.8, 300);
+  pointLight2.position.set(-113, 29, 10);
+  // light2.castShadow = true
+  pointLight2.shadow.bias = -0.05;
+  scene.add(pointLight2);
+  // const pointLightHelper2 = new THREE.PointLightHelper( pointLight2, 1 );
+  // scene.add( pointLightHelper2 );
+
+  const pointLight3 = new THREE.PointLight(0xffffff, 0.8, 100);
+  pointLight3.position.set(0, 30, 3);
+  // light2.castShadow = true
+  pointLight3.shadow.bias = -0.05;
+  scene.add(pointLight3);
+  // const pointLightHelper = new THREE.PointLightHelper( pointLight3, 1 );
+  // scene.add( pointLightHelper );
+
+  const pointLight4 = new THREE.PointLight(0xffeeee, 0.6, 100);
+  pointLight4.position.set(-14, 29, 13);
+  // light2.castShadow = true
+  pointLight4.shadow.bias = -0.05;
+  scene.add(pointLight4);
+  // const pointLightHelper4 = new THREE.PointLightHelper( pointLight4, 1 );
+  // scene.add( pointLightHelper4 );
+
+  const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 100);
+  pointLight5.position.set(80, 43, -5.3);
+  // light2.castShadow = true
+  pointLight5.shadow.bias = -0.05;
+  scene.add(pointLight5);
+  // const pointLightHelper5 = new THREE.PointLightHelper( pointLight5, 1 );
+  // scene.add( pointLightHelper5 );
+
+  const pointLight6 = new THREE.PointLight(0xffffff, 1, 300);
+  // pointLight6.position.set(-47, 49, 12.9)
+  pointLight6.position.set(-7, 40, 9);
+  // light2.castShadow = true
+  pointLight6.shadow.bias = -0.05;
+  scene.add(pointLight6);
+  // const pointLightHelper6 = new THREE.PointLightHelper( pointLight6, 1 );
+  // scene.add( pointLightHelper6 );
+
+  const pointLight7 = new THREE.PointLight(0xffffff, 0.8, 300);
+  pointLight7.position.set(45, 51, -4.1);
+  // light2.castShadow = true
+  pointLight7.shadow.bias = -0.05;
+  scene.add(pointLight7);
+  // const pointLightHelper7 = new THREE.PointLightHelper( pointLight7, 1 );
+  // scene.add( pointLightHelper7 );
+
+  const spotLight = new THREE.SpotLight();
+  spotLight.angle = Math.PI / 16;
+  spotLight.penumbra = 0;
+  // spotLight.castShadow = true;
+  spotLight.position.set(-231, 463, 687);
+  scene.add(spotLight);
+
+  // spotLight.shadow.mapSize.width = 1500;  // default
+  // spotLight.shadow.mapSize.height = 800; // default
+  spotLight.shadow.camera.near = 0.5; // default
+  spotLight.shadow.camera.far = 1000; // default
+  spotLight.shadow.focus = 1.2;
+  spotLight.shadow.bias = -0.000002;
+
+  // gui.add(pointLight6.position, 'x', -200, 200)
+  // gui.add(pointLight6.position, 'y', -200, 200)
+  // gui.add(pointLight6.position, 'z', -200, 200)
+};
+
+// 重置摄像头
+const resetCamera = () => {
+  // model.camera?.position.set(0, 0.2, 0.3);
+  model.camera.position.set(30.328, 58.993, 148.315);
+  model.camera.rotation.set(-27.88, 14.35, 7.47);
+  model.orbitControls?.update();
+  model.camera.updateProjectionMatrix();
+};
+// 设置模型位置
+const setModalPosition = () => {
+  group?.scale.set(22, 22, 22);
+  group.position.set(-10, 30, 9);
+};
+
+// // css3D文字
+// const addFm1Text = () => {
+//   fmCSS3D = new CSS3DObject(elementContent.value);
+//   fmCSS3D.scale.set(0.13, 0.13, 0.13);
+//   fmCSS3D.position.set(0, 52, 0);
+//   fmCSS3D.lookAt(model.camera.position.clone());
+//   model?.scene.add(fmCSS3D);
+// };
+
+/* 添加监控数据 */
+export const addFmText = (selectData) => {
+  if (!group) {
+    return;
+  }
+  const textArr = [
+    {
+      text: `煤矿巷道远程风门系统`,
+      font: 'normal 2.2rem Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 80,
+      y: 95,
+    },
+    {
+      text: `压力(Pa):`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 0,
+      y: 155,
+    },
+    {
+      text: `${selectData.frontRearDP}`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 290,
+      y: 155,
+    },
+    {
+      text: `动力源压力(MPa): `,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 0,
+      y: 215,
+    },
+    {
+      text: ` ${selectData.sourcePressure}`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 280,
+      y: 215,
+    },
+    {
+      text: `故障诊断:`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 0,
+      y: 275,
+    },
+    {
+      text: `${selectData.fault}`,
+      font: 'normal 30px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 280,
+      y: 275,
+    },
+    {
+      text: `煤炭科学技术研究院有限公司研制`,
+      font: 'normal 28px Arial',
+      color: '#009900',
+      strokeStyle: '#002200',
+      x: 20,
+      y: 325,
+    },
+  ];
+
+  //
+  getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
+    const textMap = track(new THREE.CanvasTexture(canvas)); // 关键一步
+    const textMaterial = track(
+      new THREE.MeshBasicMaterial({
+        // 关于材质并未讲解 实操即可熟悉                 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
+        map: textMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.FrontSide, // 这里是双面渲染的意思
+      })
+    );
+    textMaterial.blending = THREE.CustomBlending;
+    const monitorPlane = group.getObjectByName('monitorText');
+    if (monitorPlane) {
+      monitorPlane.material = textMaterial;
+    } else {
+      const planeGeometry = track(new THREE.PlaneGeometry(526, 346)); // 平面3维几何体PlaneGeometry
+      const planeMesh = track(new THREE.Mesh(planeGeometry, textMaterial));
+      planeMesh.name = 'monitorText';
+      planeMesh.scale.set(0.002, 0.002, 0.002);
+      planeMesh.position.set(-1.255, 0.09, -0.41);
+      group.add(planeMesh);
+    }
+  });
+};
+
+/* 漫游路线 */
+const createLine = () => {
+  const position = model.camera.position.clone();
+  //创建样条曲线,作为运动轨迹
+  const curve = new THREE.CatmullRomCurve3([
+    new THREE.Vector3(position.x, position.y, position.z),
+    new THREE.Vector3(26.586, 17.86, 14.144),
+    new THREE.Vector3(-0.075, 19.669, 15.051),
+    new THREE.Vector3(-154.882, 17.462, 14.981),
+    // new THREE.Vector3(76, 28, 27),
+  ]);
+  const geometry = new THREE.BufferGeometry().setFromPoints(curve.getPoints(5000));
+  // 材质对象
+  const material = new THREE.LineBasicMaterial({
+    color: 'red',
+  });
+  // 线条模型对象
+  const line = new THREE.Line(geometry, material);
+  // model?.scene.add(line) // 线条对象添加到场景中
+  return curve;
+};
+
+/* 开启漫游 */
+const enterMY = () => {
+  model.startAnimation = () => {};
+  model.camera.position.set(114.27, 15.293, 14.189);
+  model.camera.rotation.set(-86.23, 69.89, 85.98);
+  const curve = createLine();
+  let progress = 0;
+
+  model.startMY = () => {
+    if (progress <= 1 - 0.004 * 20) {
+      const point = curve.getPointAt(progress); //获取样条曲线指定点坐标,作为相机的位置
+      const pointBox = curve.getPointAt(progress + 0.004 * 20); //获取样条曲线指定点坐标
+      model.camera.position.set(point.x, point.y, point.z);
+      model.camera.lookAt(pointBox.x + 5, pointBox.y, pointBox.z);
+      // model.orbitControls.position0.set(point.x, point.y, point.z) //非必要,场景有控件时才加上
+      // model.orbitControls.target.set(pointBox.x, pointBox.y , pointBox.z) //非必要,场景有控件时才加上
+      progress += 0.004;
+    } else {
+      // progress = 0
+      model.camera.position.set(30.328, 58.993, 148.315);
+      model.camera.rotation.set(-27.88, 14.35, 7.47);
+      model.camera.lookAt(0, 0, 0);
+      model.startMY = () => {};
+      model.startAnimation = fmAnimation.bind(null);
+    }
+  };
+};
+
+/* 风门动画 */
+const render = () => {
+  if (!model) {
+    return;
+  }
+  if (isLRAnimation && group) {
+    // 左右摇摆动画
+    if (Math.abs(group.rotation.y) >= 0.2) {
+      direction = -direction;
+      group.rotation.y += 0.00005 * 30 * direction;
+    } else {
+      group.rotation.y += 0.00005 * 30 * direction;
+    }
+  }
+
+  // // //自发光
+  // const screen = group.getObjectByName('对象156');
+  // if (screen) {
+  //   model.renderer.clearDepth();
+  //   screen.layers.enable(31);
+  //   !!renderBloomPass && renderBloomPass(group);
+  // }
+
+  // 风门开关动画
+  const delta = model.clock?.getElapsedTime();
+  if (model.mixers[0]) model.mixers[0]?.update(delta);
+};
+
+// 鼠标点击、松开事件
+const mouseEvent = (event) => {
+  event.stopPropagation();
+  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
+  model.mouse.x = ((event.clientX - model.canvasContainer.getBoundingClientRect().left) / model.canvasContainer.clientWidth) * 2 - 1;
+  model.mouse.y = -((event.clientY - model.canvasContainer.getBoundingClientRect().top) / model.canvasContainer.clientHeight) * 2 + 1;
+  (model.rayCaster as THREE.Raycaster).setFromCamera(model.mouse, model.camera as THREE.Camera);
+  // 计算物体和射线的焦点
+  const intersects = model.rayCaster?.intersectObjects(group.children) as THREE.Intersection[];
+  if (intersects.length > 0) {
+    isLRAnimation = false;
+    if (animationtimer) {
+      clearTimeout(animationtimer);
+      animationtimer = null;
+    }
+
+    // 判断是否点击到视频
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+      if (mesh.name === 'player1') {
+        if (new Date().getTime() - playerStartClickTime1 < 400) {
+          // model.orbitControls?.dispatchEvent.call(model.orbitControls, { type: 'end' })
+          // 双击,视频放大
+          if (player1) {
+            player1.requestFullscreen();
+          }
+        }
+        playerStartClickTime1 = new Date().getTime();
+        return true;
+      } else if (mesh.name === 'player2') {
+        if (new Date().getTime() - playerStartClickTime2 < 400) {
+          // model.orbitControls?.dispatchEvent.call(model.orbitControls, { type: 'end' })
+          // 双击,视频放大
+          if (player2) {
+            player2.requestFullscreen();
+          }
+        }
+        playerStartClickTime2 = new Date().getTime();
+        return true;
+      }
+      return false;
+    });
+  }
+};
+
+// 初始化左右摇摆动画
+const startAnimation = () => {
+  // 开启动画
+  model.startAnimation = render.bind(null);
+  // 定义鼠标点击事件x
+  model.canvasContainer?.addEventListener('pointerdown', mouseEvent.bind(null));
+  model.canvasContainer?.addEventListener('pointerup', (event) => {
+    event.stopPropagation();
+    // 10s后开始摆动
+    if (!animationtimer && !isLRAnimation) {
+      animationtimer = setTimeout(() => {
+        isLRAnimation = true;
+      }, 10000);
+    }
+  });
+};
+
+/* 提取风门序列帧,初始化前后门动画 */
+const initAnimation = () => {
+  const tracks = model.animations[0].tracks;
+  const fontTracks: any[] = [],
+    backTracks: any[] = [],
+    arrowTracks: any[] = [];
+  for (let i = 0; i < tracks.length; i++) {
+    const track = tracks[i];
+    if (track.name.startsWith('qianmen')) {
+      fontTracks.push(track);
+    } else if (track.name.startsWith('houmen')) {
+      backTracks.push(track);
+    } else if (track.name.startsWith('Plane')) {
+      arrowTracks.push(track);
+    }
+  }
+  const frontDoor = new THREE.AnimationClip('frontDoor', 4, fontTracks);
+  const backDoor = new THREE.AnimationClip('backDoor', 4, backTracks);
+  const arr = [frontDoor, backDoor];
+  arr.forEach((animationClip) => {
+    const clipAction = model.mixers[0].clipAction(animationClip, group);
+    clipAction.clampWhenFinished = true;
+    clipAction.loop = THREE.LoopOnce;
+    if (animationClip.name == 'frontDoor') clipActionArr.frontDoor = clipAction;
+    if (animationClip.name == 'backDoor') clipActionArr.backDoor = clipAction;
+  });
+};
+
+// 播放动画
+export const play = (handlerState) => {
+  let handler = () => {};
+  switch (handlerState) {
+    case 1: // 打开前门
+      handler = () => {
+        clipActionArr.frontDoor.paused = true;
+        clipActionArr.frontDoor.reset();
+        clipActionArr.frontDoor.time = 0.5;
+        clipActionArr.frontDoor.timeScale = 0.01;
+        clipActionArr.frontDoor.clampWhenFinished = true;
+        clipActionArr.frontDoor.play();
+      };
+      break;
+    case 2: // 关闭前门
+      handler = () => {
+        clipActionArr.frontDoor.paused = true;
+        clipActionArr.frontDoor.reset(); //
+        clipActionArr.frontDoor.time = 4;
+        clipActionArr.frontDoor.timeScale = -0.01;
+        clipActionArr.frontDoor.clampWhenFinished = true;
+        clipActionArr.frontDoor.play();
+      };
+      break;
+    case 3: // 打开后门
+      handler = () => {
+        clipActionArr.backDoor.paused = true;
+        clipActionArr.backDoor.reset();
+        clipActionArr.backDoor.time = 0.5;
+        clipActionArr.backDoor.timeScale = 0.01;
+        clipActionArr.backDoor.clampWhenFinished = true;
+        clipActionArr.backDoor.play();
+      };
+      break;
+    case 4: // 关闭后门
+      handler = () => {
+        clipActionArr.backDoor.paused = true;
+        clipActionArr.backDoor.reset();
+        clipActionArr.backDoor.time = 4;
+        clipActionArr.backDoor.timeScale = -0.01;
+        clipActionArr.backDoor.clampWhenFinished = true;
+        clipActionArr.backDoor.play();
+      };
+      break;
+    case 5: // 打开前后门
+      handler = () => {
+        clipActionArr.backDoor.paused = true;
+        clipActionArr.frontDoor.paused = true;
+
+        clipActionArr.frontDoor.reset();
+        clipActionArr.frontDoor.time = 0.5;
+        clipActionArr.frontDoor.timeScale = 0.01;
+        clipActionArr.frontDoor.clampWhenFinished = true;
+        clipActionArr.frontDoor.play();
+
+        clipActionArr.backDoor.reset();
+        clipActionArr.backDoor.time = 0.5;
+        clipActionArr.backDoor.timeScale = 0.01;
+        clipActionArr.backDoor.clampWhenFinished = true;
+        clipActionArr.backDoor.play();
+      };
+      break;
+    case 6: // 关闭前后门
+      handler = () => {
+        clipActionArr.backDoor.paused = true;
+        clipActionArr.frontDoor.paused = true;
+
+        clipActionArr.frontDoor.reset();
+        clipActionArr.frontDoor.time = 4;
+        clipActionArr.frontDoor.timeScale = -0.01;
+        clipActionArr.frontDoor.clampWhenFinished = true;
+        clipActionArr.frontDoor.play();
+        clipActionArr.backDoor.reset();
+        clipActionArr.backDoor.time = 4;
+        clipActionArr.backDoor.timeScale = -0.01;
+        clipActionArr.backDoor.clampWhenFinished = true;
+        clipActionArr.backDoor.play();
+      };
+      break;
+    default:
+  }
+
+  handler();
+  model.clock.start();
+  // const honglvdeng = group.getObjectByName('honglvdeng');
+  // const material = honglvdeng.material;
+  // setTimeout(() => {
+  //   if (handlerState === 2 || handlerState === 4 || handlerState === 6) {
+  //     material.color = new THREE.Color(0x00ff00);
+  //   } else {
+  //     material.color = new THREE.Color(0xff0000);
+  //   }
+  // }, 1000);
+};
+
+// 初始化门的开关状态
+export const initOpenState = (selectData) => {
+  if (selectData.frontGateOpen == 1) {
+    clipActionArr.frontDoor.reset();
+    clipActionArr.frontDoor.time = 0.5;
+    clipActionArr.frontDoor.clampWhenFinished = true;
+    clipActionArr.frontDoor.timeScale = 1;
+    clipActionArr.frontDoor.play();
+  } else {
+    clipActionArr.frontDoor.reset();
+    clipActionArr.frontDoor.time = 4;
+    clipActionArr.frontDoor.timeScale = -1;
+    clipActionArr.frontDoor.clampWhenFinished = true;
+    clipActionArr.frontDoor.play();
+  }
+  if (selectData.rearGateOpen == 1) {
+    clipActionArr.backDoor.reset();
+    clipActionArr.backDoor.time = 0.5;
+    clipActionArr.backDoor.timeScale = 1;
+    clipActionArr.backDoor.clampWhenFinished = true;
+    clipActionArr.backDoor.play();
+  } else {
+    clipActionArr.backDoor.reset();
+    clipActionArr.backDoor.time = 4;
+    clipActionArr.backDoor.timeScale = -1;
+    clipActionArr.backDoor.clampWhenFinished = true;
+    clipActionArr.backDoor.play();
+  }
+  model.clock.start();
+};
+
+export const mountedThree = (playerVal1, playerVal2) => {
+  return new Promise((resolve) => {
+    model = new UseThree('#damper3D');
+    track = model.track;
+    model.setEnvMap('test1');
+    model.renderer.toneMappingExposure = 0.8;
+    model.setModel('fm').then((gltf) => {
+      group = gltf.scene;
+      model.scene?.add(group);
+      if (gltf.animations && gltf.animations.length > 0) {
+        model.mixers = [];
+        model.animations = [];
+        gltf.animations.forEach((animation) => {
+          const mixer = new THREE.AnimationMixer(group);
+          model.mixers.push(mixer);
+          model.animations.push(animation);
+        });
+      }
+
+      model.animate();
+      addLight(model.scene);
+      resetCamera();
+      setModalPosition();
+      startAnimation();
+      // 初始化左右摇摆动画;
+      // startAnimation();
+      initAnimation();
+
+      // renderBloomPass = createComposer(model).renderBloomPass;
+
+      // const flyLineMesh = flyLine(
+      //   [
+      //     new THREE.Vector3(-110, 0, 0),
+      //     // new THREE.Vector3(5, 4, 0),
+      //     new THREE.Vector3(120, 0, 0),
+      //   ],
+      //   '/model/hdr/y1.png'
+      // );
+      // group.add(flyLineMesh);
+
+      setTimeout(async () => {
+        player1 = playerVal1;
+        player2 = playerVal2;
+        const videoPlayer1 = document.getElementById('fm-player1')?.getElementsByClassName('vjs-tech')[0];
+        const videoPlayer2 = document.getElementById('fm-player2')?.getElementsByClassName('vjs-tech')[0];
+        if (videoPlayer1) {
+          const mesh = renderVideo(group, videoPlayer1, 'player1');
+          mesh.scale.set(-0.028, 0.0285, 1);
+          mesh.position.set(4.298, 0.02, -0.4);
+          mesh.rotation.y = -Math.PI;
+        }
+        if (videoPlayer2) {
+          const mesh = renderVideo(group, videoPlayer2, 'player2');
+          mesh.scale.set(-0.028, 0.0285, 1);
+          mesh.position.set(-4.262, 0.02, -0.4);
+          mesh.rotation.y = -Math.PI;
+        }
+        resolve(model);
+      }, 0);
+      // resolve(model);
+    });
+  });
+};
+
+export const destroy = () => {
+  if (model) {
+    model.mixers[0].uncacheClip(clipActionArr.frontDoor.getClip());
+    model.mixers[0].uncacheClip(clipActionArr.backDoor.getClip());
+    model.mixers[0].uncacheAction(clipActionArr.frontDoor, group);
+    model.mixers[0].uncacheAction(clipActionArr.backDoor, group);
+    model.mixers[0].uncacheRoot(group);
+    clipActionArr.backDoor = undefined;
+    clipActionArr.frontDoor = undefined;
+    model.animations[0].tracks = [];
+    model.mixers = [];
+    model.deleteModal();
+    model = null;
+    group = null;
+  }
+};

+ 101 - 43
src/views/vent/monitorManager/gateMonitor/index.vue

@@ -50,7 +50,7 @@
           </a-radio-group>
         </div>
       </div>
-      
+
     </div>
     <div class="title-box">
       2-2煤主辅三联巷自动风门
@@ -89,29 +89,32 @@
       </a-tabs>
     </div>
   </div>
-  
+  <div style=" z-index: -1; position: absolute; top: 50px; right: 10px; width:300px;height:280px;margin:auto" class="palyer1">
+    <LivePlayer id="fm-player1" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
+    <LivePlayer id="fm-player2" ref="player2" :videoUrl="flvURL1()" muted live loading controls style="margin-top: 10px"/>
+  </div>
 </template>
 
 <script setup lang="ts">
+  import LivePlayer from '@liveqing/liveplayer-v3'
   import '/@/assets/less/modal.less';
-  import {onBeforeMount, computed, onUnmounted, onMounted, ref, Ref, reactive, toRaw, nextTick } from 'vue';
+  import {onBeforeMount, onBeforeUnmount, computed, onUnmounted, onMounted, ref, Ref, reactive, toRaw, nextTick } from 'vue';
   import BarAndLine from '/@/components/chart/BarAndLine.vue';
   import MonitorTable from '../comment/MonitorTable.vue';
   import { initWebSocket, getRecordList } from '/@/hooks/web/useVentWebSocket';
-  import { mountedThree, addFmText, play, destroy} from './gate.threejs'
+  import { mountedThree, addFmText, play, destroy, initOpenState} from './gate.threejs'
   import { deviceControlApi } from '/@/api/vent/index';
   import { message } from 'ant-design-vue';
-  // import gsap from 'gsap';
-  // import { flyLine } from '/@/utils/threejs/FlyLine'
-
-
-
+  import { list } from "/@/views/vent/monitorManager/windowMonitor/window.api";
+  import lodash from "lodash";
 
+  const player1 = ref(null)
+  const player2 = ref(null)
   const elementContent = <Ref<HTMLElement>>ref()
   const activeKey = ref('1')
   const loading = ref(false);
-  
-  
+
+
   const propTypeArr = [
     {
       name: '气源压力(MPa)',
@@ -133,31 +136,19 @@
   const frontDoorIsOpen = ref(false); //前门是否开启
   const backDoorIsOpen = ref(false); //后门是否开启
 
-  // 监测数据
-  const selectData = reactive({
-    deviceID: '',
-    deviceType: '',
-    strname: '',
-    frontRearDP: '-', //压差
-    sourcePressure: '-', //气源压力
-    netStatus: '0', //通信状态
-    fault: '气源压力超限',
-    autoRoManual: 0
-  })
 
   const selectRowIndex = ref(0)
-
-  //  实时监测数据
-  const dataSource:any = computed(() => {
-    // const data = [...getRecordList()].reverse() || []
-    const data = [...getRecordList()] || []
-    
-    Object.assign(selectData, toRaw(data[selectRowIndex.value]))
-    // console.log(data);
-    
-    addFmText(selectData)
-    return data;
-  });  
+  const dataSource = ref([]);
+  //  webSocket 请求 实时监测数据
+  // const dataSource:any = computed(() => {
+  //   const data = [...getRecordList()] || []
+  //
+  //   Object.assign(selectData, toRaw(data[selectRowIndex.value]))
+  //
+  //   addFmText(selectData)
+  //
+  //   return data;
+  // });
 
   // echarts 图标样式
   const option = {
@@ -195,13 +186,50 @@
       }
     },
   }
-  
+
   // 设备数据
   const controlType = ref(1)
 
   const tabChange = (activeKeyVal) => {
     activeKey.value = activeKeyVal
   }
+  const initData = {
+    deviceID: '',
+    deviceType: '',
+    strname: '',
+    frontRearDP: '-', //压差
+    sourcePressure: '-', //气源压力
+    netStatus: '0', //通信状态
+    frontGateOpen: '0',
+    rearGateOpen: '0',
+    fault: '气源压力超限',
+    autoRoManual: 0
+  };
+  // 监测数据
+  const selectData = reactive(lodash.cloneDeep(initData));
+
+  // https获取监测数据
+  let timer: null | NodeJS.Timeout = null;
+  const getMonitor = () => {
+    if (Object.prototype.toString.call(timer) === '[object Null]') {
+      timer = setTimeout(() => {
+        list({ devicetype: 'gate', pagetype: 'normal' }).then((res) => {
+          dataSource.value = res.msgTxt[0].datalist || [];
+          dataSource.value.forEach((data: any) => {
+            const readData = data.readData;
+            data = Object.assign(data, readData);
+          });
+          const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
+          Object.assign(selectData, data);
+          addFmText(selectData);
+          if(timer){
+            timer = null;
+          }
+          getMonitor();
+        });
+      }, 1000);
+    }
+  };
 
   // 切换检测数据
   const getSelectRow = (selectRow, index) => {
@@ -209,10 +237,13 @@
     loading.value = true
     Object.assign(selectData, selectRow)
     setTimeout(() => {
+      frontDoorIsOpen.value = selectData.frontGateOpen === '1'
+      backDoorIsOpen.value = selectData.rearGateOpen === '1'
+      initOpenState(selectData)
       loading.value = false
     }, 300)
   }
-  
+
   // 播放动画
   const playAnimation = (handlerState) => {
     const data = {
@@ -222,7 +253,9 @@
       value: null,
       autoRoManual: selectData.autoRoManual
     };
+
     let handler = () => {};
+
     switch (handlerState) {
       case 1: // 打开前门
         if (!frontDoorIsOpen.value && !backDoorIsOpen.value) {
@@ -282,7 +315,7 @@
           if (res.success) {
           }
         })
-        .finally(() => {   
+        .finally(() => {
           handler();
           play(handlerState)
         });
@@ -301,25 +334,50 @@
       message.success('状态切换成功!')
     })
   }
-  
-  
+
+  const flvURL1 = () =>{
+    return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`
+  }
+  const flvURL2 = () =>{
+    return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`
+  }
+
+  // 视频播放
+  const addPlayVideo = () => {
+    if(player1.value.play && player2.value.play) {
+      // player1.value.setMuted(false);
+      // player2.value.setMuted(false);
+      player1.value.play();
+      player2.value.play();
+      document.body.removeEventListener('mousedown', addPlayVideo)
+    }
+  }
+
   onBeforeMount(() => {
     const sendVal = JSON.stringify({ pagetype: 'normal', devicetype: 'gate', orgcode: '', ids: '', systemID: '' });
     initWebSocket(sendVal);
-  }); 
-  
+    document.body.addEventListener('mousedown', addPlayVideo, false);
+  });
+
   onMounted(() => {
     loading.value = true;
-    mountedThree().then(() => {
-      // addFmText(selectData)
+    mountedThree(player1.value, player2.value).then(() => {
+      addFmText(selectData)
+      getMonitor()
       nextTick(() => {
         loading.value = false;
       })
     })
   });
+  onBeforeUnmount(() => {
 
+  })
   onUnmounted(() => {
     destroy()
+    if(timer) {
+      clearTimeout(timer)
+      timer = undefined;
+    }
   })
 
 </script>

+ 280 - 0
src/views/vent/monitorManager/windowMonitor/dandaoFc.threejs.ts

@@ -0,0 +1,280 @@
+import * as THREE from 'three';
+
+import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+import gsap from 'gsap';
+
+class singleWindow {
+  model;
+  modelName = 'ddFc';
+  group: THREE.Group | null = null;
+  animationTimer;
+  isLRAnimation = true;
+  direction = 1;
+  windowsActionArr = {
+    frontWindow: [],
+  };
+  player1;
+  player2;
+  playerStartClickTime1 = new Date().getTime();
+  constructor(model, playerVal1) {
+    this.model = model;
+    this.player1 = playerVal1;
+  }
+  // // 重置摄像头
+  // const resetCamera = () => {
+  //   this.model.camera.position.set(30.328, 58.993, 148.315);
+  //   this.model.camera.rotation.set(-27.88, 14.35, 7.47);
+  //   this.model.orbitControls?.update();
+  //   this.model.camera.updateProjectionMatrix();
+  // };
+
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(22, 22, 22);
+    this.group?.position.set(-35, 25, 15);
+  }
+
+  addFmText(selectData) {
+    if (!this.group) {
+      return;
+    }
+    const textArr = [
+      {
+        text: `煤矿巷道远程风窗系统`,
+        font: 'normal 2.2rem Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 90,
+        y: 95,
+      },
+      {
+        text: `过风量(m3/min):`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 150,
+      },
+      {
+        text: `${
+          selectData.frontRearDifference && selectData.rearPresentValue
+            ? Math.min(selectData.frontRearDifference, selectData.rearPresentValue)
+            : selectData.frontRearDifference || selectData.rearPresentValue || '-'
+        }`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 235,
+        y: 150,
+      },
+      {
+        text: `过风面积(m2): `,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 205,
+      },
+      {
+        text: `${selectData.forntArea && selectData.rearArea ? Math.min(selectData.forntArea, selectData.rearArea) : selectData.forntArea || selectData.rearArea || '-'}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 200,
+        y: 205,
+      },
+      {
+        text: `风窗压差(Pa):`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 256,
+      },
+      {
+        text: `${selectData.dataDh}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 200,
+        y: 256,
+      },
+      {
+        text: `调节精度:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 320,
+        y: 150,
+      },
+      {
+        text: `1% FS`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 460,
+        y: 150,
+      },
+      {
+        text: `调节范围:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 320,
+        y: 205,
+      },
+      {
+        text: `${selectData.maxarea}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 460,
+        y: 205,
+      },
+      {
+        text: `煤炭科学技术研究院有限公司研制`,
+        font: 'normal 28px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 60,
+        y: 302,
+      },
+    ];
+
+    getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
+      const textMap = new THREE.CanvasTexture(canvas); // 关键一步
+      const textMaterial = new THREE.MeshBasicMaterial({
+        // 关于材质并未讲解 实操即可熟悉                 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
+        map: textMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.DoubleSide, // 这里是双面渲染的意思
+      });
+      textMaterial.blending = THREE.CustomBlending;
+      const monitorPlane = this.group?.getObjectByName('monitorText');
+      if (monitorPlane) {
+        monitorPlane.material = textMaterial;
+      } else {
+        const planeGeometry = new THREE.PlaneGeometry(526, 346); // 平面3维几何体PlaneGeometry
+        const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
+        planeMesh.name = 'monitorText';
+        planeMesh.scale.set(0.002, 0.002, 0.002);
+        planeMesh.position.set(3.61, 0.158, -0.23);
+        this.group?.add(planeMesh);
+      }
+    });
+  }
+
+  /* 提取风门序列帧,初始化前后门动画 */
+  initAnimation() {
+    const meshArr01: THREE.Object3D[] = [];
+    const windowGroup = new THREE.Group();
+    windowGroup.name = 'hiddenGroup';
+    this.group?.children.forEach((obj) => {
+      if (obj.type === 'Mesh' && obj.name && obj.name.startsWith('shanye')) {
+        obj.rotateOnAxis(new THREE.Vector3(0, 1, 0), 0);
+        meshArr01.push(obj);
+      }
+    });
+    this.windowsActionArr.frontWindow = meshArr01;
+  }
+
+  play(rotationParam, flag) {
+    if (!this.windowsActionArr.frontWindow) {
+      return;
+    }
+    if (flag === 1) {
+      // 前风窗动画
+      this.windowsActionArr.frontWindow.forEach((mesh: THREE.Mesh) => {
+        gsap.to(mesh.rotation, {
+          y: THREE.MathUtils.degToRad(rotationParam.frontDeg1),
+          duration: (1 / 9) * Math.abs(rotationParam.frontDeg1 - mesh.rotation.y),
+          overwrite: true,
+        });
+      });
+    } else if (flag === 0) {
+      ([...this.windowsActionArr.frontWindow] as THREE.Mesh[]).forEach((mesh) => {
+        gsap.to(mesh.rotation, {
+          y: 0,
+          overwrite: true,
+        });
+      });
+    }
+  }
+
+  /* 点击风窗,风窗全屏 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    this.isLRAnimation = false;
+    if (this.animationTimer) {
+      clearTimeout(this.animationTimer);
+      this.animationTimer = null;
+    }
+    // 判断是否点击到视频
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+      if (mesh.name === 'player1') {
+        if (new Date().getTime() - this.playerStartClickTime1 < 400) {
+          // 双击,视频放大
+          if (this.player1) {
+            this.player1.requestFullscreen();
+          }
+        }
+        this.playerStartClickTime1 = new Date().getTime();
+        return true;
+      }
+      return false;
+    });
+  }
+
+  mouseUpModel() {
+    // 10s后开始摆动
+    if (!this.animationTimer && !this.isLRAnimation) {
+      this.animationTimer = setTimeout(() => {
+        this.isLRAnimation = true;
+      }, 10000);
+    }
+  }
+
+  /* 风门动画 */
+  render() {
+    if (!this.model) {
+      return;
+    }
+    if (this.isLRAnimation && this.group) {
+      // 左右摇摆动画
+      if (Math.abs(this.group.rotation.y) >= 0.2) {
+        this.direction = -this.direction;
+        this.group.rotation.y += 0.00005 * 30 * this.direction;
+      } else {
+        this.group.rotation.y += 0.00005 * 30 * this.direction;
+      }
+    }
+  }
+
+  mountedThree() {
+    return new Promise((resolve) => {
+      this.model.setModel(this.modelName).then((gltf) => {
+        this.group = gltf.scene;
+        this.setModalPosition();
+        this.initAnimation();
+
+        setTimeout(async () => {
+          const videoPlayer1 = document.getElementById('fc-player1')?.getElementsByClassName('vjs-tech')[0];
+          if (videoPlayer1) {
+            const mesh = renderVideo(this.group, videoPlayer1, 'player1');
+            mesh.scale.set(0.0382, 0.028, 0.022);
+            mesh.position.set(-1.313, 0.148, -0.22);
+          }
+
+          resolve(null);
+        }, 0);
+      });
+    });
+  }
+
+  destroy() {
+    this.windowsActionArr.frontWindow = undefined;
+    this.model = null;
+    this.group = null;
+  }
+}
+export default singleWindow;

+ 150 - 29
src/views/vent/monitorManager/windowMonitor/index.vue

@@ -20,12 +20,12 @@
       <div class="top-left row"> 井下风窗远程集中管理 </div>
       <div class="top-center row">
         <div class="input-box">
-          <span class="input-title">风窗角度:</span>
+          <span class="input-title">风窗面积:</span>
           <a-input-number placeholder="0" :min="0" :max="90" :step="1" v-model:value="windowAngle" />
         </div>
-        <div class="button-box" @click="playAnimation(1)">设定前窗面积</div>
-        <div class="button-box" @click="playAnimation(2)">设定后窗面积</div>
-        <div class="button-box" @click="playAnimation(2)" style="display: none">设定风窗面积</div>
+        <div class="button-box" @click="setArea(1)">设定前窗面积</div>
+        <div class="button-box" @click="setArea(2)">设定后窗面积</div>
+        <div class="button-box" @click="setArea(2)" style="display: none">设定风窗面积</div>
       </div>
       <div class="top-right row">
         <div class="control-type row">
@@ -72,18 +72,32 @@
       </a-tabs>
     </div>
   </div>
+  <div style="z-index: -1; position: absolute; top: 50px; right: 10px; width: 300px; height: 280px; margin: auto" class="palyer1">
+    <LivePlayer id="fc-player1" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
+    <LivePlayer id="fc-player2" ref="player2" :videoUrl="flvURL1()" muted live loading controls style="margin-top: 10px" />
+  </div>
 </template>
 
 <script setup lang="ts">
   import '/@/assets/less/modal.less';
   import BarMulti from '/@/components/chart/BarMulti.vue';
-  import { onBeforeMount, computed, ref, onMounted, nextTick, onUnmounted, reactive } from 'vue';
+  import { onBeforeMount, computed, ref, onMounted, nextTick, onUnmounted, reactive, toRaw, Ref } from 'vue';
   import MonitorTable from '../comment/MonitorTable.vue';
   import { initWebSocket, getRecordList } from '/@/hooks/web/useVentWebSocket';
-  import { mountedThree, destroy, addFmText, play } from './window.threejs';
+  import { mountedThree, destroy, addFmText, play, setModelType } from './window.threejs';
+  import { list, getTableList } from './window.api';
+  import { deviceControlApi } from '/@/api/vent/index';
+  import lodash from 'lodash';
+  import LivePlayer from '@liveqing/liveplayer-v3';
+
+  const player1 = ref(null);
+  const player2 = ref(null);
+
+  const deviceBaseList = ref([]);
   const activeKey = ref('1');
   const loading = ref(false);
   const windowAngle = ref(0);
+
   const rotationParam = {
     frontDeg0: 0, // 前门初始
     frontDeg1: windowAngle.value, // 前门目标
@@ -124,70 +138,177 @@
     },
   };
 
-  const dataSource = computed(() => {
-    return [...getRecordList()].reverse() || [];
-  });
+  // 默认初始是第一行
+  const selectRowIndex = ref(0);
+  const dataSource = ref([]);
+
+  // webSocket 请求
+  // const dataSource = computed(() => {
+  //   const data = [...getRecordList()] || [];
+  //   Object.assign(selectData, toRaw(data[selectRowIndex.value]));
+  //   addFmText(selectData);
+  //   return data;
+  // });
 
   const propTypeArr = new Map([
     ['frontPresentValue', '前窗风速'],
     ['rearPresentValue', '后窗风速'],
   ]);
 
+  const flvURL1 = () => {
+    return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`;
+  };
+  const flvURL2 = () => {
+    return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`;
+  };
   // 设备数据
   const controlType = ref(1);
 
-  // 默认初始是第一行
-  const selectRowIndex = ref(0);
-
   const tabChange = (activeKeyVal) => {
     activeKey.value = activeKeyVal;
   };
 
-  // 监测数据
-  const selectData = reactive({
+  const initData = {
     deviceID: '',
     deviceType: '',
     strname: '',
-    frontRearDP: '-', //压差
+    dataDh: '-', //压差
+    dataDtestq: '-', //测试风量
     sourcePressure: '-', //气源压力
+    dataDequivalarea: '-',
     netStatus: '0', //通信状态
     fault: '气源压力超限',
-  });
+    forntArea: '0',
+    rearArea: '0',
+    frontRearDifference: '-',
+    rearPresentValue: '-',
+    maxarea: '',
+  };
+
+  // 监测数据
+  const selectData = reactive(lodash.cloneDeep(initData));
+
+  // https获取监测数据
+  let timer: null | NodeJS.Timeout = null;
+  const getMonitor = () => {
+    if (Object.prototype.toString.call(timer) === '[object Null]') {
+      timer = setTimeout(async () => {
+        const data = await getDataSource()
+        Object.assign(selectData, data);
+        playAnimation(data, selectData.maxarea);
+        addFmText(selectData);
+        if(timer){
+          timer = null;
+        }
+        getMonitor();
+      }, 1000);
+    }
+  };
+
+  const getDataSource = async() => {
+    const res = await list({ devicetype: 'window', pagetype: 'normal' })
+    dataSource.value = res.msgTxt[0].datalist || [];
+    dataSource.value.forEach((data: any) => {
+      const readData = data.readData;
+      data = Object.assign(data, readData);
+    });
+    const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
+    return data
+  }
+
+
+  // 获取设备基本信息列表
+  const getDeviceBaseList = () => {
+    getTableList({ pageSize: 1000 }).then((res) => {
+      deviceBaseList.value = res.records;
+    });
+  };
 
   // 切换检测数据
   const getSelectRow = (selectRow, index) => {
     selectRowIndex.value = index;
     loading.value = true;
-    Object.assign(selectData, selectRow);
-    setTimeout(() => {
+    const baseData: any = deviceBaseList.value.find((baseData: any) => baseData.id === selectRow.deviceID);
+    Object.assign(selectData, initData, selectRow, baseData);
+
+    const type = selectRowIndex.value > 6 ? 'doubleWindow': 'singleWindow'
+    setModelType(type).then(() => {
+      addFmText(selectData);
+      playAnimation(selectRow, baseData.maxarea, true);
       loading.value = false;
-    }, 300);
+    })
   };
 
-  const playAnimation = (flag) => {
-    if (flag == 1) rotationParam.frontDeg1 = windowAngle.value;
-    if (flag == 2) rotationParam.backDeg1 = windowAngle.value;
-    play(rotationParam, flag).then(() => {
-      if (flag == 1) rotationParam.frontDeg0 = windowAngle.value;
-      if (flag == 2) rotationParam.backDeg0 = windowAngle.value;
-    });
+  // 判断前后窗的面积是否发生改变,如果改变则开启动画
+  const playAnimation = (data, maxarea, isFirst = false) => {
+    rotationParam.frontDeg0 = 90 / maxarea * Number(isFirst ? 0 : selectData.forntArea);
+    rotationParam.backDeg0 = 90 / maxarea * Number(isFirst ? 0 : selectData.rearArea);
+    rotationParam.frontDeg1 = 90 / maxarea * Number(data.forntArea) || 0;
+    rotationParam.backDeg1 = 90 / maxarea *  Number(data.rearArea) || 0;
+    if (!rotationParam.frontDeg1 && !rotationParam.backDeg1) {
+      play(rotationParam, 0);
+    } else {
+      if (rotationParam.frontDeg0 >= 0 && rotationParam.frontDeg1 >= 0 && rotationParam.frontDeg0 !== rotationParam.frontDeg1) {
+        setTimeout(() => {
+          play(rotationParam, 1);
+        }, 0);
+      }
+      if (rotationParam.backDeg0 >= 0 && rotationParam.backDeg1 >= 0 && rotationParam.backDeg0 !== rotationParam.backDeg1) {
+        setTimeout(() => {
+          play(rotationParam, 2);
+        }, 0);
+      }
+    }
+  };
+
+  // 设置风窗面积
+  const setArea = (flag) => {
+    const data = {
+      deviceid: selectData.deviceID,
+      devicetype: selectData.deviceType,
+      paramcode:  flag === 1 ? 'frontSetValue' : 'rearSetValue',
+      value: windowAngle.value,
+    };
+    deviceControlApi(data)
+      .then((res) => {
+        if (res.success) {
+        }
+      })
+  };
+
+  const addPlayVideo = () => {
+    if (player1.value.play && player2.value.play) {
+      if(!player1.value.paused())  player1.value.play();
+      if(!player2.value.paused())  player2.value.play();
+      document.body.removeEventListener('mousedown', addPlayVideo);
+    }
   };
 
   onBeforeMount(() => {
-    const sendVal = JSON.stringify({ pagetype: 'normal', devicetype: 'window', orgcode: '', ids: '', systemID: '' });
-    initWebSocket(sendVal);
+    // const sendVal = JSON.stringify({ pagetype: 'normal', devicetype: 'window', orgcode: '', ids: '', systemID: '' });
+    // initWebSocket(sendVal);
+
+    getDeviceBaseList();
+
+    document.body.addEventListener('mousedown', addPlayVideo, false);
   });
+
   onMounted(() => {
     loading.value = true;
-    mountedThree().then(() => {
+    mountedThree(player1.value, player2.value).then(() => {
       nextTick(() => {
         loading.value = false;
         addFmText(selectData);
+        getMonitor();
       });
     });
   });
   onUnmounted(() => {
     destroy();
+    if(timer) {
+      clearTimeout(timer)
+      timer = undefined;
+    }
   });
 </script>
 <style lang="less" scoped>

+ 310 - 0
src/views/vent/monitorManager/windowMonitor/shuangdaoFc.threejs.ts

@@ -0,0 +1,310 @@
+import * as THREE from 'three';
+
+import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+import gsap from 'gsap';
+
+class doubleWindow {
+  model;
+  modelName = 'sdFc';
+  group: THREE.Group | null = null;
+  animationTimer;
+  isLRAnimation = true;
+  direction = 1;
+  windowsActionArr = {
+    frontWindow: <THREE.Mesh[]>[],
+    backWindow: <THREE.Mesh[]>[],
+  };
+  player1;
+  player2;
+  playerStartClickTime1 = new Date().getTime();
+  playerStartClickTime2 = new Date().getTime();
+
+  constructor(model, playerVal1, playerVal2) {
+    this.model = model;
+    this.player1 = playerVal1;
+    this.player2 = playerVal2;
+  }
+
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(22, 22, 22);
+    this.group?.position.set(-35, 25, 15);
+  }
+
+  addFmText(selectData) {
+    if (!this.group) {
+      return;
+    }
+    const textArr = [
+      {
+        text: `煤矿巷道远程风窗系统`,
+        font: 'normal 2.2rem Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 90,
+        y: 95,
+      },
+      {
+        text: `过风量(m3/min):`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 150,
+      },
+      {
+        text: `${
+          selectData.frontRearDifference && selectData.rearPresentValue
+            ? Math.min(selectData.frontRearDifference, selectData.rearPresentValue)
+            : selectData.frontRearDifference || selectData.rearPresentValue || '-'
+        }`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 235,
+        y: 150,
+      },
+      {
+        text: `过风面积(m2): `,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 205,
+      },
+      {
+        text: `${selectData.forntArea && selectData.rearArea ? Math.min(selectData.forntArea, selectData.rearArea) : selectData.forntArea || selectData.rearArea || '-'}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 200,
+        y: 205,
+      },
+      {
+        text: `风窗压差(Pa):`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 256,
+      },
+      {
+        text: `${selectData.dataDh}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 200,
+        y: 256,
+      },
+      {
+        text: `调节精度:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 320,
+        y: 150,
+      },
+      {
+        text: `1% FS`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 460,
+        y: 150,
+      },
+      {
+        text: `调节范围:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 320,
+        y: 205,
+      },
+      {
+        text: `${selectData.maxarea}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 460,
+        y: 205,
+      },
+      {
+        text: `煤炭科学技术研究院有限公司研制`,
+        font: 'normal 28px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 60,
+        y: 302,
+      },
+    ];
+
+    getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
+      const textMap = new THREE.CanvasTexture(canvas); // 关键一步
+      const textMaterial = new THREE.MeshBasicMaterial({
+        // 关于材质并未讲解 实操即可熟悉                 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
+        map: textMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.DoubleSide, // 这里是双面渲染的意思
+      });
+      textMaterial.blending = THREE.CustomBlending;
+      const monitorPlane = this.group?.getObjectByName('monitorText');
+      if (monitorPlane) {
+        monitorPlane.material = textMaterial;
+      } else {
+        const planeGeometry = new THREE.PlaneGeometry(526, 346); // 平面3维几何体PlaneGeometry
+        const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
+        planeMesh.name = 'monitorText';
+        planeMesh.scale.set(0.002, 0.002, 0.002);
+        planeMesh.position.set(2.65, 0.158, -0.23);
+        this.group?.add(planeMesh);
+      }
+    });
+  }
+
+  /* 风门动画 */
+  render() {
+    if (!this.model) {
+      return;
+    }
+    if (this.isLRAnimation && this.group) {
+      // 左右摇摆动画
+      if (Math.abs(this.group.rotation.y) >= 0.2) {
+        this.direction = -this.direction;
+        this.group.rotation.y += 0.00005 * 30 * this.direction;
+      } else {
+        this.group.rotation.y += 0.00005 * 30 * this.direction;
+      }
+    }
+  }
+
+  /* 提取风门序列帧,初始化前后门动画 */
+  initAnimation() {
+    const meshArr01: THREE.Object3D[] = [];
+    const meshArr02: THREE.Object3D[] = [];
+    const windowGroup = new THREE.Group();
+    windowGroup.name = 'hiddenGroup';
+    this.group?.children.forEach((obj) => {
+      if (obj.type === 'Mesh' && obj.name && (obj.name.startsWith('shanye') || obj.name.startsWith('FCshanye'))) {
+        if (obj.name.startsWith('FCshanye')) {
+          obj.rotateOnAxis(new THREE.Vector3(0, 1, 0), 0);
+          meshArr01.push(obj);
+        } else if (obj.name.startsWith('shanye')) {
+          obj.rotateOnAxis(new THREE.Vector3(0, 1, 0), 0);
+          meshArr02.push(obj);
+        }
+      }
+    });
+    this.windowsActionArr.frontWindow = meshArr01;
+    this.windowsActionArr.backWindow = meshArr02;
+    this.group?.add(windowGroup);
+  }
+
+  /* 点击风窗,风窗全屏 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    this.isLRAnimation = false;
+    if (this.animationTimer) {
+      clearTimeout(this.animationTimer);
+      this.animationTimer = null;
+    }
+    // 判断是否点击到视频
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+      if (mesh.name === 'player1') {
+        if (new Date().getTime() - this.playerStartClickTime1 < 400) {
+          // 双击,视频放大
+          if (this.player1) {
+            this.player1.requestFullscreen();
+          }
+        }
+        this.playerStartClickTime1 = new Date().getTime();
+        return true;
+      } else if (mesh.name === 'player2') {
+        if (new Date().getTime() - this.playerStartClickTime2 < 400) {
+          // 双击,视频放大
+          if (this.player2) {
+            this.player2.requestFullscreen();
+          }
+        }
+        this.playerStartClickTime2 = new Date().getTime();
+        return true;
+      }
+      return false;
+    });
+  }
+
+  mouseUpModel() {
+    // 10s后开始摆动
+    if (!this.animationTimer && !this.isLRAnimation) {
+      this.animationTimer = setTimeout(() => {
+        this.isLRAnimation = true;
+      }, 10000);
+    }
+  }
+
+  play(rotationParam, flag) {
+    if (!this.windowsActionArr.frontWindow || !this.windowsActionArr.backWindow) {
+      return;
+    }
+    if (flag === 1) {
+      // 前风窗动画
+      this.windowsActionArr.frontWindow.forEach((mesh) => {
+        gsap.to(mesh.rotation, {
+          y: THREE.MathUtils.degToRad(rotationParam.frontDeg1),
+          duration: (1 / 9) * Math.abs(rotationParam.frontDeg1 - mesh.rotation.y),
+          overwrite: true,
+        });
+      });
+    } else if (flag === 2) {
+      // 后风窗动画
+      this.windowsActionArr.backWindow.forEach((mesh) => {
+        gsap.to(mesh.rotation, {
+          y: THREE.MathUtils.degToRad(rotationParam.backDeg1),
+          duration: (1 / 9) * Math.abs(rotationParam.backDeg1 - mesh.rotation.y),
+          overwrite: true,
+        });
+      });
+    } else if (flag === 0) {
+      ([...this.windowsActionArr.frontWindow, ...this.windowsActionArr.backWindow] as THREE.Mesh[]).forEach((mesh) => {
+        gsap.to(mesh.rotation, {
+          y: 0,
+          overwrite: true,
+        });
+      });
+    }
+  }
+
+  mountedThree() {
+    return new Promise((resolve) => {
+      this.model.setModel(this.modelName).then((gltf) => {
+        this.group = gltf.scene;
+        this.setModalPosition();
+        this.initAnimation();
+
+        setTimeout(async () => {
+          const videoPlayer1 = document.getElementById('fc-player1')?.getElementsByClassName('vjs-tech')[0];
+          const videoPlayer2 = document.getElementById('fc-player2')?.getElementsByClassName('vjs-tech')[0];
+          if (videoPlayer1) {
+            const mesh = renderVideo(this.group, videoPlayer1, 'player1');
+            mesh.scale.set(0.0385, 0.028, 0.022);
+            mesh.position.set(4.48, 0.125, -0.22);
+          }
+          if (videoPlayer2) {
+            const mesh = renderVideo(this.group, videoPlayer2, 'player2');
+            mesh.scale.set(0.0385, 0.028, 0.022);
+            mesh.position.set(-4.307, 0.145, -0.22);
+          }
+          resolve(null);
+        }, 0);
+      });
+    });
+  }
+
+  destroy() {
+    this.windowsActionArr.frontWindow = undefined;
+    this.windowsActionArr.backWindow = undefined;
+    this.model = null;
+    this.group = null;
+  }
+}
+
+export default doubleWindow;

+ 3 - 45
src/views/vent/monitorManager/windowMonitor/window.api.ts

@@ -3,58 +3,16 @@ import { Modal } from 'ant-design-vue';
 
 enum Api {
   list = '/ventanaly-device/monitor/device',
-  save = '/ventanaly-device/safety/ventanalyGate/add',
-  edit = '/ventanaly-device/safety/ventanalyGate/edit',
-  deleteById = '/ventanaly-device/safety/ventanalyGate/delete',
-  deleteBatch = '/sys/user/deleteBatch',
-  importExcel = '/sys/user/importExcel',
-  exportXls = '/sys/user/exportXls',
+  baseList = '/ventanaly-device/safety/ventanalyWindow/list',
 }
 /**
- * 导出api
- * @param params
- */
-export const getExportUrl = Api.exportXls;
-/**
- * 导入api
- */
-export const getImportUrl = Api.importExcel;
-/**
  * 列表接口
  * @param params
  */
-export const list = (params) => defHttp.get({ url: Api.list, params });
+export const list = (params) => defHttp.post({ url: Api.list, params });
 
 /**
- * 删除用户
- */
-export const deleteById = (params, handleSuccess) => {
-  return defHttp.delete({ url: Api.deleteById, params }, { joinParamsToUrl: true }).then(() => {
-    handleSuccess();
-  });
-};
-/**
- * 批量删除用户
- * @param params
- */
-export const batchDeleteById = (params, handleSuccess) => {
-  Modal.confirm({
-    title: '确认删除',
-    content: '是否删除选中数据',
-    okText: '确认',
-    cancelText: '取消',
-    onOk: () => {
-      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
-        handleSuccess();
-      });
-    },
-  });
-};
-/**
  * 保存或者更新用户
  * @param params
  */
-export const saveOrUpdate = (params, isUpdate) => {
-  const url = isUpdate ? Api.edit : Api.save;
-  return defHttp.put({ url: url, params });
-};
+export const getTableList = (params) => defHttp.get({ url: Api.baseList, params });

+ 188 - 0
src/views/vent/monitorManager/windowMonitor/window.threejs.ts

@@ -0,0 +1,188 @@
+import * as THREE from 'three';
+import UseThree from '../../../../hooks/core/threejs/useThree';
+import singleWindow from './dandaoFc.threejs'
+import doubleWindow from './shuangdaoFc.threejs'
+import {animateCamera} from '/@/utils/threejs/util'
+import gsap from "gsap";
+
+// 模型对象、 文字对象
+let model,
+  singleWindowObj,
+  doubleWindowObj,
+  group,
+  windowType = 'singleWindow',
+  oldCameraPosition = {x: 500, y:500, z:500};
+
+
+// 打灯光
+const addLight = () => {
+  const pointLight2 = new THREE.PointLight(0xffeeee, 1, 83);
+  pointLight2.position.set(-101, 34, 16);
+  pointLight2.shadow.bias = 0.05;
+  model.scene.add(pointLight2);
+
+  const pointLight3 = new THREE.PointLight(0xffffff, 1, 150);
+  pointLight3.position.set(-61, 37, 13.9);
+  pointLight3.shadow.bias = 0.05;
+  model.scene.add(pointLight3);
+
+  const pointLight4 = new THREE.PointLight(0xffeeee, 0.6, 300);
+  pointLight4.position.set(-2, 26, 20);
+  pointLight4.shadow.bias = 0.05;
+  model.scene.add(pointLight4);
+
+  const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 120);
+  pointLight5.position.set(-54, 30, 23.8);
+  pointLight5.shadow.bias = 0.05;
+  model.scene.add(pointLight5);
+
+  const pointLight7 = new THREE.PointLight(0xffffff, 1, 1000);
+  pointLight7.position.set(45, 51, -4.1);
+  pointLight7.shadow.bias = 0.05;
+  model.scene.add(pointLight7);
+
+  const spotLight = new THREE.SpotLight();
+  spotLight.angle = Math.PI / 16;
+  spotLight.penumbra = 0;
+  spotLight.castShadow = true;
+  spotLight.intensity = 1;
+  spotLight.position.set(-231, 463, 687);
+  model.scene.add(spotLight);
+
+  spotLight.shadow.camera.near = 0.5; // default
+  spotLight.shadow.camera.far = 1000; // default
+  spotLight.shadow.focus = 1.2;
+  spotLight.shadow.bias = -0.000002;
+};
+
+// // 重置摄像头
+// const resetCamera = () => {
+//   model.camera.position.set(30.328, 58.993, 148.315);
+//   model.camera.rotation.set(-31.85, 30.07, 17.29);
+//   model.orbitControls?.update();
+//   model.camera.updateProjectionMatrix();
+// };
+
+// 初始化左右摇摆动画
+const startAnimation = () => {
+  // 定义鼠标点击事件
+  model.canvasContainer?.addEventListener('mousedown', mouseEvent.bind(null));
+  model.canvasContainer?.addEventListener('pointerup', (event) => {
+    event.stopPropagation();
+    // 单道、 双道
+    if(windowType === 'doubleWindow') {
+      doubleWindowObj.mouseUpModel.call(doubleWindowObj)
+    }else if(windowType === 'singleWindow') {
+      singleWindowObj.mouseUpModel.call(singleWindowObj)
+    }
+  });
+};
+
+// 鼠标点击、松开事件
+const mouseEvent = (event) => {
+  event.stopPropagation();
+  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
+  model.mouse.x = ((event.clientX - model.canvasContainer.getBoundingClientRect().left) / model.canvasContainer.clientWidth) * 2 - 1;
+  model.mouse.y = -((event.clientY - model.canvasContainer.getBoundingClientRect().top) / model.canvasContainer.clientHeight) * 2 + 1;
+  (model.rayCaster as THREE.Raycaster).setFromCamera(model.mouse, model.camera as THREE.Camera);
+  if(group){
+    const intersects = model.rayCaster?.intersectObjects(group.children, false) as THREE.Intersection[];
+    if (intersects.length > 0) {
+      // 单道、 双道
+      if(windowType === 'doubleWindow') {
+        doubleWindowObj.mousedownModel.call(doubleWindowObj, intersects)
+      }else if(windowType === 'singleWindow') {
+        singleWindowObj.mousedownModel.call(singleWindowObj, intersects)
+      }
+    }
+  }
+};
+
+export const addFmText = (selectData) => {
+  if(windowType === 'doubleWindow') {
+    return doubleWindowObj.addFmText.call(doubleWindowObj, selectData);
+  }else{
+    return singleWindowObj.addFmText.call(singleWindowObj, selectData);
+  }
+}
+
+export const play = (rotationParam, flag) => {
+  if(windowType === 'doubleWindow') {
+    return doubleWindowObj.play.call(doubleWindowObj, rotationParam, flag);
+  }else{
+    return singleWindowObj.play.call(singleWindowObj, rotationParam, flag);
+  }
+}
+
+// 切换风窗类型
+export const setModelType = (type) => {
+  windowType = type
+  model.camera.position.set(500, 500, 500);
+
+  return new Promise((resolve) => {
+    // 显示双道风窗
+    if(windowType === 'doubleWindow') {
+      model.startAnimation = doubleWindowObj.render.bind(doubleWindowObj);
+      group = doubleWindowObj.group
+      if( model.scene.getObjectByName('ddFc')){
+        model.scene.remove(singleWindowObj.group)
+      }
+      setTimeout(() => {
+        resolve(null)
+
+        const position = doubleWindowObj.group
+        animateCamera(oldCameraPosition, oldCameraPosition, {x: 66.257, y: 57.539, z: 94.313}, {x: position.x, y: position.y, z: position.z }, model)
+        model.scene.add(doubleWindowObj.group)
+      }, 300)
+
+    }else if(windowType === 'singleWindow') {
+      // 显示单道风窗
+      model.startAnimation = singleWindowObj.render.bind(singleWindowObj);
+      group = singleWindowObj.group
+      if( model.scene.getObjectByName('sdFc')){
+        model.scene.remove(doubleWindowObj.group)
+      }
+      setTimeout(() => {
+        resolve(null)
+        const position = {x:0, y:0, z:0}
+        animateCamera(oldCameraPosition, {x:0, y:0, z:0}, {x: 66.257, y: 57.539, z: 94.313}, {x: position.x, y: position.y, z: position.z }, model)
+        model.scene.add(singleWindowObj.group)
+      }, 300)
+    }
+  })
+}
+
+export const mountedThree = (playerVal1, playerVal2) => {
+  return new Promise(async (resolve) => {
+    model = new UseThree('#window3D');
+    model.setEnvMap('test1');
+    model.renderer.toneMappingExposure = 0.8;
+
+    addLight()
+    // resetCamera()
+
+    // 单道、 双道
+    doubleWindowObj = new doubleWindow(model, playerVal1, playerVal2)
+    singleWindowObj = new singleWindow(model, playerVal1)
+    await doubleWindowObj.mountedThree()
+    await singleWindowObj.mountedThree()
+
+    await setModelType(windowType)
+
+    startAnimation()
+    setTimeout(() => {
+      model.animate();
+    }, 0)
+    resolve(null)
+  });
+};
+
+export const destroy = () => {
+  if (model) {
+    model.deleteModal1();
+    model = null;
+    group = null
+    singleWindowObj = null
+    doubleWindowObj = null
+  }
+};

+ 309 - 8
src/views/vent/monitorManager/windrectMonitor/index.vue

@@ -1,25 +1,326 @@
 <template>
-  <lineMulti :chartData="dataSource" xAxisPropType="strname" :propTypeArr="propTypeArr" height="40vh" width="100%" />
-  <MonitorTable columnsType="windrect_monitor" :dataSource="dataSource" design-scope="windrect-monitor" title="测风装置监测" />
+  <div class="bg" style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden">
+    <a-spin :spinning="loading" />
+    <div id="window3D" v-show="!loading" style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
+    <!-- <div id="damper3DCSS" v-show="!loading" style="width: 100%; height: 100%; top:0; left: 0; position: absolute; overflow: hidden;">
+      <div>
+        <div ref="elementContent" class="elementContent">
+          <p><span class="data-title">压力(Pa):</span>{{selectData.frontRearDP}}</p>
+          <p><span class="data-title">动力源压力(MPa):</span>{{selectData.sourcePressure}}</p>
+          <p><span class="data-title">故障诊断:</span>
+            <i
+              :class="{'state-icon': true, 'open': selectData.messageBoxStatus, 'close': !selectData.messageBoxStatus}"
+            ></i>{{selectData.fault}}</p>
+        </div>
+      </div>
+    </div> -->
+  </div>
+  <div class="scene-box">
+    <div class="top-box">
+      <div class="top-left row"> 井下测风装置集中管理 </div>
+      <div class="top-center row">
+        <div class="button-box" @click="start(1)">一键测风</div>
+        <div class="button-box" @click="start(0)">复位</div>
+        <div class="button-box" @click="testPlay()">自测动画</div>
+        <div class="button-box" @click="testPlay('up')">上</div>
+        <div class="button-box" @click="testPlay('center')">中</div>
+        <div class="button-box" @click="testPlay('down')">下</div>
+        <div class="button-box" @click="testPlay('reset')">复位</div>
+      </div>
+      <div class="top-right row">
+        <div class="control-type row">
+          <div class="control-title">控制模式:</div>
+          <a-radio-group v-model:value="controlType">
+            <a-radio :value="1">就地</a-radio>
+            <a-radio :value="2">远程</a-radio>
+          </a-radio-group>
+        </div>
+        <div class="run-type row">
+          <div class="control-title">运行状态:</div>
+          <a-radio-group v-model:value="controlType">
+            <a-radio :value="1">检修</a-radio>
+          </a-radio-group>
+        </div>
+        <div class="run-state row">
+          <div class="control-title">网络状态:</div>
+          <a-radio-group v-model:value="controlType">
+            <a-radio :value="1">运行</a-radio>
+          </a-radio-group>
+        </div>
+      </div>
+    </div>
+    <div class="title-box"> 2-2煤主辅三联巷自动风窗 </div>
+    <div class="tabs-box">
+      <a-tabs v-model:activeKey="activeKey" @change="tabChange">
+        <a-tab-pane key="1" tab="实时监测">
+          <MonitorTable columnsType="windrect_monitor" :dataSource="dataSource" design-scope="windrect-monitor" @selectRow="getSelectRow" title="测风装置监测" />
+        </a-tab-pane>
+        <a-tab-pane key="2" tab="实时曲线图" force-render>
+          <div class="tab-item" v-if="activeKey === '2'">
+            <lineMulti :chartData="dataSource" xAxisPropType="strname" :propTypeArr="propTypeArr" height="40vh" width="100%" />
+          </div>
+        </a-tab-pane>
+        <a-tab-pane key="3" tab="历史数据">
+          <div class="tab-item"> Content of Tab Pane 2 </div>
+        </a-tab-pane>
+        <a-tab-pane key="4" tab="操作历史">
+          <div class="tab-item"> Content of Tab Pane 2 </div>
+        </a-tab-pane>
+        <a-tab-pane key="5" tab="实时报警">
+          <div class="tab-item"> Content of Tab Pane 2 </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
+  </div>
+  <div style=" z-index: -1; position: absolute; top: 50px; right: 10px; width:300px;height:280px;margin:auto" class="palyer">
+    <LivePlayer id="cf-player1" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
+    <LivePlayer id="cf-player2" ref="player2" :videoUrl="flvURL1()" muted live loading controls style="margin-top: 10px"/>
+  </div>
 </template>
 
 <script setup lang="ts">
-  import { onBeforeMount, computed } from 'vue';
+  import '/@/assets/less/modal.less';
   import lineMulti from '/@/components/chart/LineMulti.vue';
+  import { onBeforeMount, computed, ref, onMounted, nextTick, onUnmounted, reactive, toRaw } from 'vue';
   import MonitorTable from '../comment/MonitorTable.vue';
   import { initWebSocket, getRecordList } from '/@/hooks/web/useVentWebSocket';
-  const dataSource = computed(() => {
-    return [...getRecordList()].reverse() || [];
+  import { deviceControlApi } from '/@/api/vent/index';
+  import { mountedThree, destroy, addFmText, play, setModelType } from './windrect.threejs';
+  import LivePlayer from '@liveqing/liveplayer-v3'
+  import { list } from "/@/views/vent/monitorManager/windowMonitor/window.api";
+  import { initOpenState } from "/@/views/vent/monitorManager/gateMonitor/gate.threejs";
+
+  const player1 = ref(null)
+  const player2 = ref(null)
+  const activeKey = ref('1');
+  const loading = ref(false);
+  // 默认初始是第一行
+  const selectRowIndex = ref(0);
+  // 监测数据
+  const selectData = reactive({
+    deviceID: '',
+    deviceType: '',
+    strname: '',
+    dataDh: '-', //压差
+    dataDtestq: '-', //测试风量
+    sourcePressure: '-', //气源压力
+    dataDequivalarea: '-',
+    netStatus: '0', //通信状态
+    fault: '气源压力超限',
   });
+  const flvURL1 = () =>{
+    return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`
+  }
+  const flvURL2 = () =>{
+    return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`
+  }
+
+  // const dataSource = computed(() => {
+  //   const data = [...getRecordList()] || [];
+  //   Object.assign(selectData, toRaw(data[selectRowIndex.value]));
+  //   addFmText(selectData);
+  //   return data;
+  // });
+
+  const dataSource = ref([]);
+
   const propTypeArr = new Map([
     ['incipientWindSpeed1', 'V1风速'],
     ['incipientWindSpeed2', 'V2风速'],
     ['incipientWindSpeed3', 'V3风速'],
     ['sourcePressure', '气源压力'],
   ]);
+  const tabChange = (activeKeyVal) => {
+    activeKey.value = activeKeyVal;
+  };
+
+  // 设备数据
+  const controlType = ref(1);
+
+  // https获取监测数据
+  let timer: null | NodeJS.Timeout = null;
+  const getMonitor = () => {
+    if (Object.prototype.toString.call(timer) === '[object Null]') {
+      timer = setTimeout(() => {
+        list({ devicetype: 'windrect', pagetype: 'normal' }).then((res) => {
+          dataSource.value = res.msgTxt[0].datalist || [];
+          dataSource.value.forEach((data: any) => {
+            const readData = data.readData;
+            data = Object.assign(data, readData);
+          });
+          const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
+          Object.assign(selectData, data);
+          addFmText(selectData);
+          // 根据3个点位分别执行动画
+
+          if(timer){
+            timer = null;
+          }
+          getMonitor();
+        });
+      }, 1000);
+    }
+  };
+
+  // 自测动画方法
+  const testPlay = (flag) => {
+    play(flag)
+    // setTimeout(() => {
+    //   play('up')
+    // }, 0)
+    // setTimeout(() => {
+    //   play('center')
+    // }, 10000)
+    // setTimeout(() => {
+    //   play('down')
+    // }, 40000)
+    // setTimeout(() => {
+    //   play('up')
+    // }, 60000)
+  }
+
+  // 切换检测数据
+  const getSelectRow = (selectRow, index) => {
+    selectRowIndex.value = index
+    loading.value = true
+    Object.assign(selectData, selectRow)
+    const type = selectRowIndex.value < 6 ? 'lmWindRect': 'zdWindRect'
+    setModelType(type).then(() => {
+      addFmText(selectData);
+      loading.value = false;
+    })
+  }
+
+  const start = (flag) => {
+    const data = {
+      deviceid: selectData.deviceID,
+      devicetype: selectData.deviceType,
+      paramcode:  flag == 1 ? 'testStart' : '',
+    };
+    deviceControlApi(data)
+      .then((res) => {
+        if (res.success) {
+          //
+        }
+      })
+  }
+
+  const addPlayVideo = () => {
+    if(player1.value.play && player2.value.play) {
+      // player1.value.setMuted(false);
+      // player2.value.setMuted(false);
+      player1.value.play();
+      player2.value.play();
+      document.body.removeEventListener('mousedown', addPlayVideo)
+    }
+  }
+
   onBeforeMount(() => {
-    const sendVal = JSON.stringify({ pagetype: 'normal', devicetype: 'windrect', orgcode: '', ids: '', systemID: '' });
-    initWebSocket(sendVal);
+    // const sendVal = JSON.stringify({ pagetype: 'normal', devicetype: 'windrect', orgcode: '', ids: '', systemID: '' });
+    // initWebSocket(sendVal);
+
+    document.body.addEventListener('mousedown', addPlayVideo, false);
+  });
+
+  onMounted(() => {
+    loading.value = true;
+    mountedThree(player1.value, player2.value).then(() => {
+      nextTick(() => {
+        loading.value = false;
+        getMonitor()
+        addFmText(selectData);
+      });
+    });
+  });
+  onUnmounted(() => {
+    destroy();
+    if(timer) {
+      clearTimeout(timer)
+      timer = undefined;
+    }
   });
 </script>
-<style scoped lang="scss"></style>
+<style scoped lang="less">
+  .input-box {
+    display: flex;
+    align-items: center;
+    .input-title {
+      color: rgb(0, 255, 242);
+      width: auto;
+    }
+    margin-right: 10px;
+  }
+
+  :deep(.jeecg-basic-table .ant-table-wrapper) {
+    background-color: #ffffff00;
+  }
+  :deep(.ant-tabs-bar) {
+    margin: 0;
+  }
+  :deep(.ant-table) {
+    background-color: #ffffff00 !important;
+    color: #fff;
+  }
+  :deep(.ant-table-header) {
+    background-color: transparent;
+    // height: 42px;
+  }
+  :deep(.ant-table-thead > tr > th) {
+    background-color: transparent;
+    border: none;
+  }
+  :deep(.ant-table-body > tr > th) {
+    background-color: transparent;
+    border: none;
+  }
+  :deep(.ant-table-body > tr > td) {
+    border: none;
+  }
+  :deep(.ant-table-fixed-header > .ant-table-content > .ant-table-scroll > .ant-table-body) {
+    background-color: #ffffff05;
+    margin-top: 8px;
+    &::-webkit-scrollbar {
+      display: none;
+    }
+  }
+
+  :deep(.jeecg-basic-table .ant-table-wrapper .ant-table-title) {
+    padding: 0;
+  }
+  :deep(.jeecg-basic-table-row__striped td) {
+    background-color: transparent;
+  }
+  :deep(.ant-table-tbody > tr:hover.ant-table-row > td) {
+    background-color: #ffffff22;
+  }
+  :deep(.ant-table-tbody > tr:hover.ant-table-row > th) {
+    background-color: #ffffff22;
+  }
+  :deep(.ant-table-thead > tr:hover.ant-table-row > td) {
+    background-color: #ffffff22;
+  }
+  :deep(.ant-table-tbody > tr.ant-table-row-selected td) {
+    background-color: #ffffff22;
+  }
+  :deep(.ant-table-tbody > tr > td) {
+    border-color: #ffffff22;
+  }
+  :deep(.ant-table-thead > tr > th:hover) {
+    background-color: transparent !important;
+  }
+  :deep(.ant-table-thead > tr > th) {
+    color: #fff;
+  }
+  :deep(.ant-table-fixed-header .ant-table-scroll .ant-table-header) {
+    background: #ffffff44;
+    position: relative;
+    z-index: 999;
+    padding: 4px 0 !important;
+    &::-webkit-scrollbar {
+      display: none;
+    }
+  }
+  :deep(.ant-tabs-nav) {
+    color: #fff;
+  }
+</style>

+ 302 - 0
src/views/vent/monitorManager/windrectMonitor/longmen.threejs.ts

@@ -0,0 +1,302 @@
+import * as THREE from 'three';
+
+import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+import gsap from 'gsap';
+
+class lmWindRect {
+  model;
+  modelName = 'lmcf';
+  group: THREE.Group | null = null;
+  animationTimer;
+  isLRAnimation = true;
+  direction = 1;
+  player1;
+  player2;
+  playerStartClickTime1 = new Date().getTime();
+  playerStartClickTime2 = new Date().getTime();
+
+  constructor(model, playerVal1, playerVal2) {
+    this.model = model;
+    this.player1 = playerVal1;
+    this.player2 = playerVal2;
+  }
+
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.position.set(-35, 25, 15);
+  }
+
+  addFmText(selectData) {
+    if (!this.group) {
+      return;
+    }
+    const textArr = [
+      {
+        text: `煤矿巷道远程风窗系统`,
+        font: 'normal 2.2rem Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 90,
+        y: 95,
+      },
+      {
+        text: `过风量(m3/min):`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 115,
+      },
+      {
+        text: `${
+          selectData.frontRearDifference && selectData.rearPresentValue
+            ? Math.min(selectData.frontRearDifference, selectData.rearPresentValue)
+            : selectData.frontRearDifference || selectData.rearPresentValue || '-'
+        }`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 235,
+        y: 115,
+      },
+      {
+        text: `过风面积(m2): `,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 182,
+      },
+      {
+        text: `${selectData.forntArea && selectData.rearArea ? Math.min(selectData.forntArea, selectData.rearArea) : selectData.forntArea || selectData.rearArea || '-'}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 200,
+        y: 182,
+      },
+      {
+        text: `风窗压差(Pa):`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 245,
+      },
+      {
+        text: `${selectData.dataDh}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 200,
+        y: 245,
+      },
+      {
+        text: `调节精度:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 320,
+        y: 115,
+      },
+      {
+        text: `1% FS`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 460,
+        y: 115,
+      },
+      {
+        text: `调节范围:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 320,
+        y: 182,
+      },
+      {
+        text: `${selectData.maxarea}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 460,
+        y: 182,
+      },
+      {
+        text: `煤炭科学技术研究院有限公司研制`,
+        font: 'normal 28px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 60,
+        y: 302,
+      },
+    ];
+
+    getTextCanvas(560, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
+      const textMap = new THREE.CanvasTexture(canvas); // 关键一步
+      const textMaterial = new THREE.MeshBasicMaterial({
+        map: textMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.DoubleSide, // 这里是双面渲染的意思
+      });
+      textMaterial.blending = THREE.CustomBlending;
+      const monitorPlane = this.group?.getObjectByName('monitorText');
+      if (monitorPlane) {
+        monitorPlane.material = textMaterial;
+      } else {
+        const planeGeometry = new THREE.PlaneGeometry(560, 346); // 平面3维几何体PlaneGeometry
+        const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
+        planeMesh.name = 'monitorText';
+        planeMesh.scale.set(0.045, 0.045, 0.045);
+        planeMesh.position.set(-27.26, 0.848, -10.46);
+        this.group?.add(planeMesh);
+      }
+    });
+  }
+
+  /* 风门动画 */
+  render() {
+    if (!this.model) {
+      return;
+    }
+    if (this.isLRAnimation && this.group) {
+      // 左右摇摆动画
+      if (Math.abs(this.group.rotation.y) >= 0.2) {
+        this.direction = -this.direction;
+        this.group.rotation.y += 0.00005 * 30 * this.direction;
+      } else {
+        this.group.rotation.y += 0.00005 * 30 * this.direction;
+      }
+    }
+  }
+
+  /* 提取风门序列帧,初始化前后门动画 */
+  initAnimation() {
+    const windGroup = new THREE.Group();
+    windGroup.name = 'lmTanTou';
+    this.group?.children.forEach((obj) => {
+      if (obj.type === 'Mesh' && obj.name && obj.name.startsWith('LMtantou')) {
+        if (obj.name.startsWith('LMtantou')) {
+          windGroup.add(obj.clone());
+          this.group?.remove(obj);
+        }
+      }
+    });
+    this.group?.add(windGroup);
+  }
+
+  /* 点击风窗,风窗全屏 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    this.isLRAnimation = false;
+    if (this.animationTimer) {
+      clearTimeout(this.animationTimer);
+      this.animationTimer = null;
+    }
+    // 判断是否点击到视频
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+      if (mesh.name === 'player1') {
+        if (new Date().getTime() - this.playerStartClickTime1 < 400) {
+          // 双击,视频放大
+          if (this.player1) {
+            this.player1.requestFullscreen();
+          }
+        }
+        this.playerStartClickTime1 = new Date().getTime();
+        return true;
+      } else if (mesh.name === 'player2') {
+        if (new Date().getTime() - this.playerStartClickTime2 < 400) {
+          // 双击,视频放大
+          if (this.player2) {
+            this.player2.requestFullscreen();
+          }
+        }
+        this.playerStartClickTime2 = new Date().getTime();
+        return true;
+      }
+      return false;
+    });
+  }
+
+  mouseUpModel() {
+    // 10s后开始摆动
+    if (!this.animationTimer && !this.isLRAnimation) {
+      this.animationTimer = setTimeout(() => {
+        this.isLRAnimation = true;
+      }, 10000);
+    }
+  }
+
+  resetModel() {
+    clearTimeout(this.animationTimer);
+    this.isLRAnimation = false;
+  }
+
+  // 播放动画
+  play(flag) {
+    const cfTanTou = this.group?.getObjectByName('lmTanTou') as THREE.Group;
+    if (!cfTanTou) return;
+    switch (flag) {
+      case 'up':
+        gsap.to(cfTanTou['position'], {
+          y: 0,
+          duration: Math.abs(cfTanTou['position']['y'] - 0) / 14,
+          ease: 'easeQutQuad',
+          overwrite: true,
+        });
+        break;
+      case 'center':
+        gsap.to(cfTanTou['position'], {
+          y: -7,
+          duration: Math.abs(cfTanTou['position']['y'] + 7) / 14,
+          ease: 'easeQutQuad',
+          overwrite: true,
+        });
+        break;
+      case 'down':
+        gsap.to(cfTanTou['position'], {
+          y: -14,
+          duration: Math.abs(cfTanTou['position']['y'] + 14) / 14,
+          ease: 'easeQutCubic',
+          overwrite: true,
+        });
+        break;
+    }
+  }
+
+  mountedThree() {
+    return new Promise((resolve) => {
+      this.model.setModel(this.modelName).then((gltf) => {
+        this.group = gltf.scene;
+        this.setModalPosition();
+        this.initAnimation();
+
+        setTimeout(async () => {
+          const videoPlayer1 = document.getElementById('cf-player1')?.getElementsByClassName('vjs-tech')[0];
+          const videoPlayer2 = document.getElementById('cf-player2')?.getElementsByClassName('vjs-tech')[0];
+          if (videoPlayer1) {
+            const mesh = renderVideo(this.group, videoPlayer1, 'player1');
+            mesh.scale.set(1.07, 0.92, 1);
+            mesh.position.set(93.73, 0.465, -9.62);
+            this.group?.add(mesh);
+          }
+          if (videoPlayer2) {
+            const mesh = renderVideo(this.group, videoPlayer2, 'player2');
+            mesh.scale.set(1.07, 0.92, 1);
+            mesh.position.set(-86.77, 0.405, -9.62);
+            this.group?.add(mesh);
+          }
+          resolve(null);
+        }, 0);
+      });
+    });
+  }
+
+  destroy() {
+    this.model = null;
+    this.group = null;
+  }
+}
+
+export default lmWindRect;

+ 191 - 0
src/views/vent/monitorManager/windrectMonitor/windrect.threejs.ts

@@ -0,0 +1,191 @@
+import * as THREE from 'three';
+import { animateCamera, getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+import UseThree from '../../../../hooks/core/threejs/useThree';
+import lmWindRect from './longmen.threejs';
+import zdWindRect from './zhedie.threejs';
+import gsap from 'gsap';
+import * as dat from 'dat.gui';
+const gui = new dat.GUI();
+
+// 模型对象、 文字对象
+let model, //
+  group,
+  lmWindRectObj,
+  zdWindRectObj,
+  windRectType = 'lmWindRect';
+
+// 打灯光
+const addLight = () => {
+  const pointLight2 = new THREE.PointLight(0xffeeee, 1.5, 100);
+  pointLight2.position.set(-120, 16, -33);
+  pointLight2.shadow.bias = 0.05;
+  model.scene.add(pointLight2);
+
+  const pointLight3 = new THREE.PointLight(0xffffff, 1, 40);
+  pointLight3.position.set(-66, 40, 1);
+  pointLight3.shadow.bias = 0.05;
+  model.scene.add(pointLight3);
+
+  const pointLight4 = new THREE.PointLight(0xffeeee, 0.6, 230);
+  pointLight4.position.set(-18, 30, 12);
+  pointLight4.shadow.bias = 0.05;
+  model.scene.add(pointLight4);
+
+  const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 90);
+  pointLight5.position.set(-57, 7, -30);
+  pointLight5.shadow.bias = 0.05;
+  model.scene.add(pointLight5);
+
+  const pointLight6 = new THREE.PointLight(0xffffff, 1.5, 270);
+  pointLight6.position.set(72, -33, 11.4);
+  pointLight6.shadow.bias = 0.05;
+  model.scene.add(pointLight6);
+
+  const pointLight7 = new THREE.PointLight(0xffffff, 1, 300);
+  pointLight7.position.set(45, 51, -4.1);
+  pointLight7.shadow.bias = -0.05;
+  model.scene.add(pointLight7);
+
+  const spotLight = new THREE.SpotLight();
+  spotLight.angle = Math.PI / 16;
+  spotLight.penumbra = 0;
+  spotLight.castShadow = true;
+  spotLight.position.set(-231, 463, 687);
+  model.scene.add(spotLight);
+
+  spotLight.shadow.camera.near = 0.5; // default
+  spotLight.shadow.camera.far = 1000; // default
+  spotLight.shadow.focus = 1;
+  spotLight.shadow.bias = -0.000002;
+
+  // gui.add(pointLight6.position, 'x', -200, 200);
+  // gui.add(pointLight6.position, 'y', -200, 200);
+  // gui.add(pointLight6.position, 'z', -200, 200);
+  // gui.add(pointLight6, 'distance', 0, 500);
+};
+
+// 重置摄像头
+const resetCamera = () => {
+  model.camera.position.set(30.328, 58.993, 148.315);
+  model.camera.rotation.set(-27.88, 14.35, 7.47);
+  model.orbitControls?.update();
+  model.camera.updateProjectionMatrix();
+};
+
+// 初始化左右摇摆动画
+const startAnimation = () => {
+  // 定义鼠标点击事件
+  model.canvasContainer?.addEventListener('mousedown', mouseEvent.bind(null));
+  model.canvasContainer?.addEventListener('pointerup', (event) => {
+    event.stopPropagation();
+    // 单道、 双道
+    if (windRectType === 'lmWindRect') {
+      lmWindRectObj.mouseUpModel.call(lmWindRectObj);
+    } else if (windRectType === 'zdWindRect') {
+      zdWindRectObj.mouseUpModel.call(zdWindRectObj);
+    }
+  });
+};
+
+// 鼠标点击、松开事件
+const mouseEvent = (event) => {
+  event.stopPropagation();
+  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
+  model.mouse.x = ((event.clientX - model.canvasContainer.getBoundingClientRect().left) / model.canvasContainer.clientWidth) * 2 - 1;
+  model.mouse.y = -((event.clientY - model.canvasContainer.getBoundingClientRect().top) / model.canvasContainer.clientHeight) * 2 + 1;
+  (model.rayCaster as THREE.Raycaster).setFromCamera(model.mouse, model.camera as THREE.Camera);
+  if (group) {
+    const intersects = model.rayCaster?.intersectObjects(group.children, false) as THREE.Intersection[];
+    if (intersects.length > 0) {
+      // 单道、 双道
+      if (windRectType === 'lmWindRect') {
+        lmWindRectObj.mousedownModel.call(lmWindRectObj, intersects);
+      } else if (windRectType === 'zdWindRect') {
+        zdWindRectObj.mousedownModel.call(zdWindRectObj, intersects);
+      }
+    }
+  }
+};
+
+/* 添加监控数据 */
+export const addFmText = (selectData) => {
+  if (windRectType === 'lmWindRect') {
+    return lmWindRectObj.addFmText.call(lmWindRectObj, selectData);
+  } else if (windRectType === 'zdWindRect') {
+    return zdWindRectObj.addFmText.call(zdWindRectObj, selectData);
+  }
+};
+
+export const play = (flag) => {
+  if (windRectType === 'lmWindRect') {
+    return lmWindRectObj.play.call(lmWindRectObj, flag);
+  } else if (windRectType === 'zdWindRect') {
+    return zdWindRectObj.play.call(zdWindRectObj, flag);
+  }
+};
+
+// 切换风窗类型
+export const setModelType = (type) => {
+  windRectType = type;
+  model.camera.position.set(1000, 1000, 1000);
+  return new Promise((resolve) => {
+    // 显示双道风窗
+    if (windRectType === 'lmWindRect') {
+      model.startAnimation = lmWindRectObj.render.bind(lmWindRectObj);
+      group = lmWindRectObj.group;
+      if (model.scene.getObjectByName('zdcf')) {
+        model.scene.remove(zdWindRectObj.group);
+      }
+      setTimeout(() => {
+        resolve(null);
+        const position = lmWindRectObj.group;
+        const oldCameraPosition = { x: 0, y: 0, z: 0 };
+        animateCamera(oldCameraPosition, oldCameraPosition, { x: 66.257, y: 57.539, z: 94.313 }, { x: position.x, y: position.y, z: position.z }, model);
+        model.scene.add(lmWindRectObj.group);
+      }, 300);
+    } else if (windRectType === 'zdWindRect') {
+      model.startAnimation = zdWindRectObj.render.bind(zdWindRectObj);
+      group = zdWindRectObj.group;
+      if (model.scene.getObjectByName('lmcf')) {
+        model.scene.remove(lmWindRectObj.group);
+      }
+      setTimeout(() => {
+        resolve(null);
+        const position = zdWindRectObj.group;
+        const oldCameraPosition = { x: 0, y: 0, z: 0 };
+        animateCamera(oldCameraPosition, oldCameraPosition, { x: 66.257, y: 57.539, z: 94.313 }, { x: position.x, y: position.y, z: position.z }, model);
+        model.scene.add(zdWindRectObj.group);
+      }, 300);
+    }
+  });
+};
+
+export const mountedThree = (playerVal1, playerVal2) => {
+  return new Promise(async (resolve) => {
+    model = new UseThree('#window3D');
+    model.setEnvMap('test1');
+    model.renderer.toneMappingExposure = 0.8;
+
+    addLight();
+    // resetCamera();
+    lmWindRectObj = new lmWindRect(model, playerVal1, playerVal2);
+    await lmWindRectObj.mountedThree();
+    zdWindRectObj = new zdWindRect(model, playerVal1);
+    await zdWindRectObj.mountedThree();
+
+    await setModelType(windRectType);
+    startAnimation();
+    setTimeout(() => {
+      model.animate();
+    }, 0);
+    resolve(null);
+  });
+};
+
+export const destroy = () => {
+  if (model) {
+    model.deleteModal1();
+    model = null;
+    group = null;
+  }
+};

+ 291 - 0
src/views/vent/monitorManager/windrectMonitor/zhedie.threejs.ts

@@ -0,0 +1,291 @@
+import * as THREE from 'three';
+
+import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+import gsap from 'gsap';
+
+class zdWindRect {
+  model;
+  modelName = 'zdcf';
+  group: THREE.Group | null = null;
+  mixers: THREE.AnimationMixer[] = [];
+  animations: THREE.AnimationClip[] = [];
+  animationAction: THREE.AnimationAction | null = null;
+  animationTimer;
+  isLRAnimation = true;
+  direction = 1;
+  player1;
+  playerStartClickTime1 = new Date().getTime();
+
+  constructor(model, playerVal1) {
+    this.model = model;
+    this.player1 = playerVal1;
+  }
+
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(22, 22, 22);
+    this.group?.position.set(-35, 25, 15);
+  }
+
+  addFmText(selectData) {
+    if (!this.group) {
+      return;
+    }
+    const textArr = [
+      {
+        text: `煤矿巷道远程风窗系统`,
+        font: 'normal 2.2rem Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 90,
+        y: 95,
+      },
+      {
+        text: `过风量(m3/min):`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 150,
+      },
+      {
+        text: `${
+          selectData.frontRearDifference && selectData.rearPresentValue
+            ? Math.min(selectData.frontRearDifference, selectData.rearPresentValue)
+            : selectData.frontRearDifference || selectData.rearPresentValue || '-'
+        }`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 235,
+        y: 150,
+      },
+      {
+        text: `过风面积(m2): `,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 205,
+      },
+      {
+        text: `${selectData.forntArea && selectData.rearArea ? Math.min(selectData.forntArea, selectData.rearArea) : selectData.forntArea || selectData.rearArea || '-'}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 200,
+        y: 205,
+      },
+      {
+        text: `风窗压差(Pa):`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 256,
+      },
+      {
+        text: `${selectData.dataDh}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 200,
+        y: 256,
+      },
+      {
+        text: `调节精度:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 320,
+        y: 150,
+      },
+      {
+        text: `1% FS`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 460,
+        y: 150,
+      },
+      {
+        text: `调节范围:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 320,
+        y: 205,
+      },
+      {
+        text: `${selectData.maxarea}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 460,
+        y: 205,
+      },
+      {
+        text: `煤炭科学技术研究院有限公司研制`,
+        font: 'normal 28px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 60,
+        y: 302,
+      },
+    ];
+
+    getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
+      const textMap = new THREE.CanvasTexture(canvas); // 关键一步
+      const textMaterial = new THREE.MeshBasicMaterial({
+        map: textMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.DoubleSide, // 这里是双面渲染的意思
+      });
+      textMaterial.blending = THREE.CustomBlending;
+      const monitorPlane = this.group?.getObjectByName('monitorText');
+      if (monitorPlane) {
+        monitorPlane.material = textMaterial;
+      } else {
+        const planeGeometry = new THREE.PlaneGeometry(526, 346); // 平面3维几何体PlaneGeometry
+        const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
+        planeMesh.name = 'monitorText';
+        planeMesh.scale.set(0.002, 0.002, 0.002);
+        planeMesh.position.set(-3.6, -0.123, -0.41);
+        this.group?.add(planeMesh);
+      }
+    });
+  }
+
+  /* 风门动画 */
+  render() {
+    if (!this.model) {
+      return;
+    }
+    if (this.isLRAnimation && this.group) {
+      // 左右摇摆动画
+      if (Math.abs(this.group.rotation.y) >= 0.2) {
+        this.direction = -this.direction;
+        this.group.rotation.y += 0.00005 * 30 * this.direction;
+      } else {
+        this.group.rotation.y += 0.00005 * 30 * this.direction;
+      }
+    }
+
+    if (this.mixers[0]) this.mixers[0]?.update(1 / 25);
+  }
+
+  /* 提取风门序列帧,初始化前后门动画 */
+  initAnimation() {
+    this.animationAction = this.mixers[0].clipAction(this.animations[0]);
+    this.animationAction.clampWhenFinished = true;
+    this.animationAction.loop = THREE.LoopOnce;
+  }
+
+  /* 点击风窗,风窗全屏 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    this.isLRAnimation = false;
+    if (this.animationTimer) {
+      clearTimeout(this.animationTimer);
+      this.animationTimer = null;
+    }
+    // 判断是否点击到视频
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+      if (mesh.name === 'player1') {
+        if (new Date().getTime() - this.playerStartClickTime1 < 400) {
+          // 双击,视频放大
+          if (this.player1) {
+            this.player1.requestFullscreen();
+          }
+        }
+        this.playerStartClickTime1 = new Date().getTime();
+        return true;
+      }
+      return false;
+    });
+  }
+
+  mouseUpModel() {
+    // 10s后开始摆动
+    if (!this.animationTimer && !this.isLRAnimation) {
+      this.animationTimer = setTimeout(() => {
+        this.isLRAnimation = true;
+      }, 10000);
+    }
+  }
+
+  resetModel() {
+    clearTimeout(this.animationTimer);
+    this.isLRAnimation = false;
+  }
+
+  // 播放动画
+  play(flag) {
+    if (flag === 'up') {
+      this.animationAction?.reset();
+      // @ts-ignore
+      this.animationAction.time = 0;
+      this.animations[0].duration = 200 / 25;
+      // this.mixers[0].timeScale = 0.1;
+      this.animationAction?.play();
+    } else if (flag === 'center') {
+      this.animationAction?.reset();
+      // @ts-ignore
+      this.animationAction.time = 200 / 25;
+      this.animations[0].duration = 300 / 25;
+      // this.mixers[0].timeScale = 0.1;
+      this.animationAction?.play();
+    } else if (flag === 'down') {
+      this.animationAction?.reset();
+      // @ts-ignore
+      this.animationAction.time = 300 / 25;
+      this.animations[0].duration = 450 / 25;
+      // this.mixers[0].timeScale = 0.1;
+      this.animationAction?.play();
+    } else {
+      this.animationAction?.reset();
+      // @ts-ignore
+      this.animationAction.time = 450 / 25;
+      this.animations[0].duration = 530 / 25;
+      // this.mixers[0].timeScale = 0.1;
+      this.animationAction?.play();
+    }
+  }
+
+  mountedThree() {
+    return new Promise((resolve) => {
+      this.model.setModel(this.modelName).then((gltf) => {
+        this.group = gltf.scene;
+        if (gltf.animations && gltf.animations.length > 0) {
+          gltf.animations.forEach((animation) => {
+            const mixer = new THREE.AnimationMixer(gltf.scene);
+            this.mixers.push(mixer);
+            this.animations.push(animation);
+            console.log(this.mixers, this.animations);
+          });
+        }
+        console.log(gltf.animations);
+        this.setModalPosition();
+        this.initAnimation();
+
+        setTimeout(async () => {
+          const videoPlayer1 = document.getElementById('cf-player1')?.getElementsByClassName('vjs-tech')[0];
+          if (videoPlayer1) {
+            const mesh = renderVideo(this.group, videoPlayer1, 'player1');
+            mesh.scale.set(0.0385, 0.028, 0.022);
+            mesh.position.set(4.792, -0.16, -0.4);
+            this.group?.add(mesh);
+          }
+          resolve(null);
+        }, 0);
+      });
+    });
+  }
+
+  destroy() {
+    this.model = null;
+    this.group = null;
+  }
+}
+
+export default zdWindRect;

Some files were not shown because too many files changed in this diff