fanLocal.three.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. import * as THREE from 'three';
  2. import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
  3. import { animateCamera, getTextCanvas, renderVideo, updateAxisCenter } from '/@/utils/threejs/util';
  4. import UseThree from '../../../../utils/threejs/useThree';
  5. import fcFan from './fcfanLocal.three';
  6. import fmFan from './fmfanLocal.three';
  7. import Smoke from '/@/views/vent/comment/threejs/Smoke';
  8. import gsap from 'gsap';
  9. import useEvent from '../../../../utils/threejs/useEvent';
  10. // import * as dat from 'dat.gui';
  11. // const gui = new dat.GUI();
  12. // gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
  13. const modelName = 'jbfj-hd';
  14. // 模型对象、 文字对象
  15. let model: UseThree | undefined,
  16. group: THREE.Object3D | undefined,
  17. fcFanObj: fcFan | undefined,
  18. fmFanObj: fmFan | undefined,
  19. player1,
  20. fanType,
  21. topSmoke: Smoke | undefined,
  22. downSmoke: Smoke | undefined,
  23. playerStartClickTime1 = new Date().getTime();
  24. const { mouseDownFn, mousemoveFn, mouseUpFn } = useEvent();
  25. // 打灯光
  26. const addLight = () => {
  27. const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
  28. directionalLight.position.set(-564, 602, -261);
  29. group?.add(directionalLight);
  30. // directionalLight.target = group;
  31. // const spotLight = new THREE.SpotLight();
  32. // spotLight.angle = Math.PI / 16;
  33. // spotLight.penumbra = 0;
  34. // spotLight.castShadow = true;
  35. // spotLight.position.set(0, 463, 687);
  36. // scene.add(spotLight);
  37. // spotLight.shadow.camera.near = 0.5; // default
  38. // spotLight.shadow.camera.far = 1000; // default
  39. // spotLight.shadow.focus = 1;
  40. // spotLight.shadow.bias = -0.000002;
  41. // spotLight.target = group;
  42. // const pointLight6 = new THREE.PointLight(0xffffff, 1, 500);
  43. // pointLight6.position.set(-131, 64, -1);
  44. // pointLight6.shadow.bias = 0.05;
  45. // group?.add(pointLight6);
  46. // gui.add(directionalLight.position, 'x', -1000, 1000);
  47. // gui.add(directionalLight.position, 'y', -1000, 1000);
  48. // gui.add(directionalLight.position, 'z', -1000, 1000);
  49. // gui.add(pointLight6.position, 'x', -500, 500);
  50. // gui.add(pointLight6.position, 'y', -500, 500);
  51. // gui.add(pointLight6.position, 'z', -500, 500);
  52. // gui.add(pointLight6, 'distance', 0, 500);
  53. };
  54. // 重置摄像头
  55. const resetCamera = () => {
  56. model.camera.position.set(0, -1000, 100);
  57. model.camera.far = 1000;
  58. model.orbitControls?.update();
  59. model.camera.updateProjectionMatrix();
  60. };
  61. // 设置模型位置
  62. const setModalPosition = () => {
  63. if (!group) return;
  64. group.position.set(0, 10, -50);
  65. group.rotation.y = Math.PI / 2;
  66. };
  67. const setControls = () => {
  68. model.orbitControls.panSpeed = 0.5;
  69. model.orbitControls.rotateSpeed = 0.5;
  70. model.orbitControls.maxPolarAngle = Math.PI / 2.4;
  71. model.orbitControls.minPolarAngle = Math.PI / 3;
  72. };
  73. // 切换局部通风机类型
  74. export const setModelType = (type) => {
  75. fanType = type;
  76. return new Promise((resolve) => {
  77. // 显示双道风窗
  78. let childGroup;
  79. if (fanType === 'fm' && fcFanObj && fcFanObj.group) {
  80. if (group.getObjectByName('jbfj-fc')) {
  81. group.remove(fcFanObj.group);
  82. }
  83. childGroup = fmFanObj.group;
  84. (group?.getObjectByName('text5')).visible = false;
  85. (group?.getObjectByName('text4')).visible = true;
  86. } else if (fanType === 'fc' && fmFanObj && fmFanObj.group) {
  87. // 显示单道风窗
  88. if (group.getObjectByName('jbfj-fm')) {
  89. group.remove(fmFanObj.group);
  90. }
  91. childGroup = fcFanObj.group;
  92. (group?.getObjectByName('text5')).visible = true;
  93. (group?.getObjectByName('text4')).visible = false;
  94. }
  95. setTimeout(async () => {
  96. group?.add(childGroup);
  97. const oldCameraPosition = { x: 615, y: 275, z: 744 };
  98. await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, { x: 0.08, y: 21.73, z: 78.45 }, { x: 0.13, y: -0.82, z: 0.236 }, model, 0.8);
  99. resolve(null);
  100. }, 300);
  101. });
  102. };
  103. /* 添加监控数据 */
  104. export const addText = (selectData) => {
  105. if (!group) {
  106. return;
  107. }
  108. const textArr = [
  109. {
  110. text: `煤矿巷道远程局部风机系统`,
  111. font: 'normal 30px Arial',
  112. color: '#009900',
  113. strokeStyle: '#002200',
  114. x: 50,
  115. y: 110,
  116. },
  117. {
  118. text: `进风量(m³/min):`,
  119. font: 'normal 30px Arial',
  120. color: '#009900',
  121. strokeStyle: '#002200',
  122. x: 0,
  123. y: 165,
  124. },
  125. {
  126. text: `${selectData.windQuantity1}`,
  127. font: 'normal 30px Arial',
  128. color: '#009900',
  129. strokeStyle: '#002200',
  130. x: 290,
  131. y: 165,
  132. },
  133. {
  134. text: `供风量(m³/min): `,
  135. font: 'normal 30px Arial',
  136. color: '#009900',
  137. strokeStyle: '#002200',
  138. x: 0,
  139. y: 220,
  140. },
  141. {
  142. text: ` ${selectData.windQuantity2}`,
  143. font: 'normal 30px Arial',
  144. color: '#009900',
  145. strokeStyle: '#002200',
  146. x: 280,
  147. y: 220,
  148. },
  149. {
  150. text: `故障诊断:`,
  151. font: 'normal 30px Arial',
  152. color: '#009900',
  153. strokeStyle: '#002200',
  154. x: 0,
  155. y: 275,
  156. },
  157. {
  158. text: `${selectData.fault}`,
  159. font: 'normal 30px Arial',
  160. color: '#009900',
  161. strokeStyle: '#002200',
  162. x: 280,
  163. y: 275,
  164. },
  165. {
  166. text: `煤炭科学技术研究院有限公司研制`,
  167. font: 'normal 28px Arial',
  168. color: '#009900',
  169. strokeStyle: '#002200',
  170. x: 20,
  171. y: 325,
  172. },
  173. ];
  174. getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
  175. const textMap = new THREE.CanvasTexture(canvas); // 关键一步
  176. const textMaterial = new THREE.MeshBasicMaterial({
  177. // 关于材质并未讲解 实操即可熟悉 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
  178. map: textMap, // 设置纹理贴图
  179. transparent: true,
  180. side: THREE.FrontSide, // 这里是双面渲染的意思
  181. });
  182. textMaterial.blending = THREE.CustomBlending;
  183. const monitorPlane = group.getObjectByName('monitorText');
  184. if (monitorPlane) {
  185. monitorPlane.material = textMaterial;
  186. } else {
  187. const planeGeometry = new THREE.PlaneGeometry(526, 346); // 平面3维几何体PlaneGeometry
  188. const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
  189. planeMesh.name = 'monitorText';
  190. planeMesh.scale.set(0.0135, 0.0135, 0.0135);
  191. planeMesh.rotation.y = -Math.PI / 2;
  192. planeMesh.position.set(-84.79, 0.82, 17.01);
  193. group.add(planeMesh);
  194. }
  195. });
  196. };
  197. // // css3D文字
  198. export const addCssText = () => {
  199. if (!group) return;
  200. if (!group.getObjectByName('text1')) {
  201. const element = document.getElementById('inputBox') as HTMLElement;
  202. const fanLocalCSS3D = new CSS3DObject(element);
  203. fanLocalCSS3D.name = 'text1';
  204. fanLocalCSS3D.scale.set(0.04, 0.04, 0.04);
  205. fanLocalCSS3D.rotation.y = -Math.PI / 2;
  206. fanLocalCSS3D.position.set(-85.68, 5.97, -17.74);
  207. group.add(fanLocalCSS3D);
  208. }
  209. if (!group.getObjectByName('text2')) {
  210. const element = document.getElementById('outBox') as HTMLElement;
  211. const fanLocalCSS3D = new CSS3DObject(element);
  212. fanLocalCSS3D.name = 'text2';
  213. fanLocalCSS3D.scale.set(0.1, 0.1, 0.1);
  214. fanLocalCSS3D.rotation.y = -Math.PI / 2;
  215. fanLocalCSS3D.position.set(74.63, 13.54, 3.84);
  216. group.add(fanLocalCSS3D);
  217. }
  218. if (!group.getObjectByName('text3')) {
  219. const element = document.getElementById('returnBox') as HTMLElement;
  220. const fanLocalCSS3D = new CSS3DObject(element);
  221. fanLocalCSS3D.name = 'text3';
  222. fanLocalCSS3D.scale.set(0.1, 0.1, 0.1);
  223. fanLocalCSS3D.rotation.y = -Math.PI / 2;
  224. fanLocalCSS3D.position.set(35.28, 10.05, -37.23);
  225. group.add(fanLocalCSS3D);
  226. }
  227. if (!group.getObjectByName('text4')) {
  228. const element = document.getElementById('gateBox') as HTMLElement;
  229. if (element) {
  230. // element.innerHTML = '';
  231. const fanLocalCSS3D = new CSS3DObject(element);
  232. fanLocalCSS3D.name = 'text4';
  233. fanLocalCSS3D.scale.set(0.04, 0.04, 0.04);
  234. fanLocalCSS3D.rotation.y = -Math.PI / 2;
  235. fanLocalCSS3D.position.set(-73.13, 8.44, -23.52);
  236. group.add(fanLocalCSS3D);
  237. }
  238. }
  239. if (!group.getObjectByName('text5')) {
  240. const element = document.getElementById('windownBox') as HTMLElement;
  241. if (element) {
  242. // element.innerHTML = '';
  243. const fanLocalCSS3D = new CSS3DObject(element);
  244. fanLocalCSS3D.name = 'text5';
  245. fanLocalCSS3D.scale.set(0.07, 0.07, 0.07);
  246. fanLocalCSS3D.rotation.y = -Math.PI / 2;
  247. fanLocalCSS3D.position.set(-28.44, 9.78, -40.42);
  248. group.add(fanLocalCSS3D);
  249. }
  250. }
  251. };
  252. export const playSmoke = (controlType, deviceType, frequency, state) => {
  253. if (frequency) {
  254. setSmokeFrequency(deviceType, frequency);
  255. }
  256. if (controlType === 'startSmoke') {
  257. runFly(deviceType, state);
  258. }
  259. };
  260. const initFly = async () => {
  261. const topCurve = [
  262. {
  263. path0: new THREE.Vector3(-89.84, 2.359, 4.91),
  264. path1: new THREE.Vector3(-85.678, 2.359, 3.61),
  265. isSpread: true,
  266. spreadDirection: -1, //
  267. },
  268. {
  269. path0: new THREE.Vector3(-85.678, 2.352, 3.66),
  270. path1: new THREE.Vector3(-85.636, 2.353, -3.829),
  271. isSpread: false,
  272. spreadDirection: 0,
  273. },
  274. {
  275. path0: new THREE.Vector3(-85.636, 2.353, -3.829),
  276. path1: new THREE.Vector3(-85.636, 1.026, -5.881),
  277. isSpread: false,
  278. spreadDirection: 0,
  279. },
  280. {
  281. path0: new THREE.Vector3(-85.636, 1.026, -5.881),
  282. path1: new THREE.Vector3(-85.618, 0.887, -12.862),
  283. isSpread: false,
  284. spreadDirection: 0,
  285. },
  286. {
  287. path0: new THREE.Vector3(-85.618, 0.827, -12.962),
  288. path1: new THREE.Vector3(80.404, 0.827, -12.962),
  289. isSpread: false,
  290. spreadDirection: 0,
  291. },
  292. {
  293. path0: new THREE.Vector3(80.404, 0.827, -12.962),
  294. path1: new THREE.Vector3(93.164, 0.85, -12.962),
  295. isSpread: true,
  296. spreadDirection: 1, // 1是由小变大,-1是由大变小
  297. },
  298. ];
  299. const downCurve = [
  300. {
  301. path0: new THREE.Vector3(-94.84, -0.388, 3.61),
  302. path1: new THREE.Vector3(-85.678, -0.393, 3.61),
  303. isSpread: true,
  304. spreadDirection: -1, //
  305. },
  306. {
  307. path0: new THREE.Vector3(-85.678, -0.393, 3.275),
  308. path1: new THREE.Vector3(-85.636, -0.392, -3.829),
  309. isSpread: false,
  310. spreadDirection: 0,
  311. },
  312. {
  313. path0: new THREE.Vector3(-85.636, -0.392, -3.829),
  314. path1: new THREE.Vector3(-85.636, 0.926, -5.881),
  315. isSpread: false,
  316. spreadDirection: 0,
  317. },
  318. {
  319. path0: new THREE.Vector3(-85.636, 1.026, -5.881),
  320. path1: new THREE.Vector3(-85.618, 0.887, -12.862),
  321. isSpread: false,
  322. spreadDirection: 0,
  323. },
  324. {
  325. path0: new THREE.Vector3(-85.618, 0.887, -12.962),
  326. path1: new THREE.Vector3(80.404, 0.887, -12.962),
  327. isSpread: false,
  328. spreadDirection: 0,
  329. },
  330. {
  331. path0: new THREE.Vector3(80.404, 0.887, -12.962),
  332. path1: new THREE.Vector3(93.164, 0.91, -12.962),
  333. isSpread: true,
  334. spreadDirection: 1, // 1是由小变大,-1是由大变小
  335. },
  336. ];
  337. if (!topSmoke) {
  338. topSmoke = new Smoke('/model/img/texture-smoke.png', '#ffffff', 0, 0.8, 0.5, 400);
  339. topSmoke.setPath(topCurve);
  340. await topSmoke.setPoints();
  341. group?.add(topSmoke.points);
  342. }
  343. if (!downSmoke) {
  344. downSmoke = new Smoke('/model/img/texture-smoke.png', '#ffffff', 0, 0.8, 0.5, 400);
  345. downSmoke.setPath(downCurve);
  346. await downSmoke.setPoints();
  347. group?.add(downSmoke.points);
  348. }
  349. };
  350. const runFly = (deviceType, state) => {
  351. if (state === 'open') {
  352. if (deviceType === 'top') {
  353. if (downSmoke.frameId) {
  354. downSmoke.stopSmoke();
  355. }
  356. topSmoke.startSmoke();
  357. } else {
  358. if (topSmoke.frameId) {
  359. topSmoke.stopSmoke();
  360. }
  361. downSmoke.startSmoke();
  362. }
  363. } else {
  364. if (downSmoke.frameId) {
  365. downSmoke.stopSmoke();
  366. }
  367. if (topSmoke.frameId) {
  368. topSmoke.stopSmoke();
  369. }
  370. }
  371. };
  372. // 调频 30-50 (life 300 - 800) , 25 = (800 - 300)/ 20
  373. const setSmokeFrequency = (deviceType, frequency) => {
  374. const life = (frequency - 30) * 25;
  375. const duration = (Math.abs(life - topSmoke.life) / 500) * 25;
  376. let smoke;
  377. if (deviceType === 'top') {
  378. smoke = topSmoke;
  379. } else {
  380. smoke = downSmoke;
  381. }
  382. gsap.to(smoke, {
  383. life: life,
  384. duration: duration,
  385. ease: 'easeInCubic',
  386. overwrite: true,
  387. });
  388. };
  389. const clearFly = () => {
  390. if (topSmoke) topSmoke.clearSmoke();
  391. if (downSmoke) downSmoke.clearSmoke();
  392. };
  393. // 初始化事件
  394. const startAnimation = () => {
  395. // 定义鼠标点击事件
  396. model.canvasContainer?.addEventListener('mousedown', mouseEvent.bind(null));
  397. model.canvasContainer?.addEventListener('pointerup', mouseUp);
  398. };
  399. // 鼠标点击、松开事件
  400. const mouseEvent = (event) => {
  401. if (event.button == 0) {
  402. model?.canvasContainer?.addEventListener('mousemove', mousemove);
  403. mouseDownFn(<UseThree>model, <THREE.Object3D>group, event, (intersects) => {
  404. intersects.find((intersect) => {
  405. const mesh = intersect.object;
  406. if (mesh.name === 'player1') {
  407. if (new Date().getTime() - playerStartClickTime1 < 400) {
  408. // 双击,视频放大
  409. if (player1) {
  410. player1.requestFullscreen();
  411. }
  412. }
  413. playerStartClickTime1 = new Date().getTime();
  414. return true;
  415. }
  416. });
  417. });
  418. console.log('摄像头控制信息', model?.orbitControls, model?.camera);
  419. }
  420. };
  421. const mouseUp = () => {
  422. if (!model) return;
  423. mouseUpFn(model);
  424. model.canvasContainer?.removeEventListener('mousemove', mousemove);
  425. };
  426. const mousemove = () => {
  427. mousemoveFn();
  428. };
  429. export const mountedThree = (playerVal1) => {
  430. player1 = playerVal1;
  431. return new Promise((resolve) => {
  432. model = new UseThree('#fanLocal3D', '#fanLocal3DCSS');
  433. model.setEnvMap('test1');
  434. model.renderer.toneMappingExposure = 1.0;
  435. if (model.renderer) {
  436. model.renderer.sortObjects = true;
  437. }
  438. resetCamera();
  439. model.setGLTFModel([modelName]).then(async (gltf) => {
  440. group = <THREE.Object3D>gltf[0];
  441. const Fengtongbu01 = group?.getObjectByName('Cylinder1054') as THREE.Mesh;
  442. if (Fengtongbu01) {
  443. const textMaterial = new THREE.MeshBasicMaterial({
  444. color: '#000',
  445. transparent: true,
  446. opacity: 0.3,
  447. side: THREE.DoubleSide, // 这里是双面渲染的意思
  448. });
  449. Fengtongbu01.material = textMaterial;
  450. Fengtongbu01.renderOrder = 300;
  451. }
  452. model?.scene?.add(group);
  453. setModalPosition();
  454. setControls();
  455. await initFly();
  456. model?.animate();
  457. addLight(model?.scene);
  458. fcFanObj = new fcFan(model);
  459. await fcFanObj.mountedThree();
  460. fmFanObj = new fmFan(model);
  461. await fmFanObj.mountedThree();
  462. const videoPlayer1 = document.getElementById('jb-player1')?.getElementsByClassName('vjs-tech')[0];
  463. if (videoPlayer1) {
  464. const mesh = renderVideo(group, videoPlayer1, 'player1');
  465. if (mesh) {
  466. mesh.scale.set(0.222, 0.19, 0.2);
  467. mesh.position.set(-84.87, 0.298, 24.76);
  468. mesh.rotation.y = -Math.PI / 2;
  469. group.add(mesh);
  470. }
  471. } else {
  472. const textArr = [
  473. {
  474. text: `无信号输入`,
  475. font: 'normal 40px Arial',
  476. color: '#009900',
  477. strokeStyle: '#002200',
  478. x: 170,
  479. y: 40,
  480. },
  481. ];
  482. getTextCanvas(560, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
  483. const textMap = new THREE.CanvasTexture(canvas); // 关键一步
  484. const textMaterial = new THREE.MeshBasicMaterial({
  485. map: textMap, // 设置纹理贴图
  486. transparent: true,
  487. side: THREE.DoubleSide, // 这里是双面渲染的意思
  488. });
  489. textMaterial.blending = THREE.CustomBlending;
  490. const monitorPlane = group?.getObjectByName('noPlayer');
  491. if (monitorPlane) {
  492. monitorPlane.material = textMaterial;
  493. } else {
  494. const planeGeometry = new THREE.PlaneGeometry(100, 100); // 平面3维几何体PlaneGeometry
  495. const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
  496. if (!videoPlayer1) {
  497. planeMesh.name = 'noPlayer';
  498. planeMesh.scale.set(0.07, 0.05, 0.07);
  499. planeMesh.position.set(-84.82, -1.53, 24.94);
  500. planeMesh.rotation.y = -Math.PI / 2;
  501. group?.add(planeMesh.clone());
  502. }
  503. }
  504. });
  505. }
  506. startAnimation();
  507. resolve(model);
  508. });
  509. });
  510. };
  511. export const destroy = () => {
  512. if (model) {
  513. model.isRender = false;
  514. console.log('场景销毁后信息----------->', model.renderer?.info);
  515. clearFly();
  516. topSmoke = undefined;
  517. downSmoke = undefined;
  518. if (fcFanObj) fcFanObj.destroy();
  519. if (fmFanObj) fmFanObj.destroy();
  520. group = undefined;
  521. model.destroy();
  522. model = undefined;
  523. }
  524. };