longmen.threejs.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. import * as THREE from 'three';
  2. import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
  3. import gsap from 'gsap';
  4. class lmWindRect {
  5. model;
  6. modelName = 'lmcf';
  7. group: THREE.Object3D = new THREE.Object3D();
  8. animationTimer;
  9. isLRAnimation = true;
  10. direction = 1;
  11. player1;
  12. player2;
  13. playerStartClickTime1 = new Date().getTime();
  14. playerStartClickTime2 = new Date().getTime();
  15. deviceRunState = '';
  16. constructor(model, playerVal1, playerVal2) {
  17. this.model = model;
  18. this.player1 = playerVal1;
  19. this.player2 = playerVal2;
  20. this.group.name = this.modelName;
  21. }
  22. addLight() {
  23. const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
  24. directionalLight.position.set(5.3, 9, 0.8);
  25. this.group.add(directionalLight);
  26. directionalLight.target = this.group;
  27. // gui.add(directionalLight.position, 'x', -10, 20).onChange(function (value) {
  28. // directionalLight.position.x = Number(value);
  29. // _this.render();
  30. // });
  31. // gui.add(directionalLight.position, 'y', -50, 50).onChange(function (value) {
  32. // directionalLight.position.y = Number(value);
  33. // _this.render();
  34. // });
  35. // gui.add(directionalLight.position, 'z', -20, 20).onChange(function (value) {
  36. // directionalLight.position.z = Number(value);
  37. // _this.render();
  38. // });
  39. const spotLight = new THREE.SpotLight();
  40. spotLight.angle = Math.PI / 16;
  41. spotLight.penumbra = 0;
  42. spotLight.castShadow = true;
  43. spotLight.distance = 0;
  44. spotLight.position.set(-470, -500, 500);
  45. this.group.add(spotLight);
  46. spotLight.shadow.camera.near = 0.5; // default
  47. spotLight.shadow.camera.far = 1000; // default
  48. spotLight.shadow.focus = 1;
  49. spotLight.shadow.bias = -0.000002;
  50. }
  51. // 设置模型位置
  52. setModalPosition() {
  53. this.group?.scale.set(22, 22, 22);
  54. this.group?.position.set(-25, 25, 15);
  55. }
  56. addMonitorText(selectData) {
  57. if (!this.group) {
  58. return;
  59. }
  60. const textArr = [
  61. {
  62. text: `龙门式测风装置`,
  63. font: 'normal 32px Arial',
  64. color: '#009900',
  65. strokeStyle: '#002200',
  66. x: 170,
  67. y: 40,
  68. },
  69. {
  70. text: `风量(m3/min):`,
  71. font: 'normal 29px Arial',
  72. color: '#009900',
  73. strokeStyle: '#002200',
  74. x: 2,
  75. y: 115,
  76. },
  77. {
  78. text: `${selectData.m3 ? selectData.m3 : '-'}`,
  79. font: 'normal 29px Arial',
  80. color: '#009900',
  81. strokeStyle: '#002200',
  82. x: 200,
  83. y: 115,
  84. },
  85. {
  86. text: `气源压力(MPa): `,
  87. font: 'normal 29px Arial',
  88. color: '#009900',
  89. strokeStyle: '#002200',
  90. x: 2,
  91. y: 182,
  92. },
  93. {
  94. text: `${selectData.temperature ? selectData.temperature : '-'}`,
  95. font: 'normal 29px Arial',
  96. color: '#009900',
  97. strokeStyle: '#002200',
  98. x: 215,
  99. y: 182,
  100. },
  101. {
  102. text: `Va(m/s):`,
  103. font: 'normal 29px Arial',
  104. color: '#009900',
  105. strokeStyle: '#002200',
  106. x: 2,
  107. y: 245,
  108. },
  109. {
  110. text: `${selectData.va ? selectData.va : '-'}`,
  111. font: 'normal 29px Arial',
  112. color: '#009900',
  113. strokeStyle: '#002200',
  114. x: 130,
  115. y: 246,
  116. },
  117. {
  118. text: `V1(m/s):`,
  119. font: 'normal 28px Arial',
  120. color: '#009900',
  121. strokeStyle: '#002200',
  122. x: 331,
  123. y: 115,
  124. },
  125. {
  126. text: `${selectData.incipientWindSpeed1 ? selectData.incipientWindSpeed1 : '-'}`,
  127. font: 'normal 28px Arial',
  128. color: '#009900',
  129. strokeStyle: '#002200',
  130. x: 455,
  131. y: 115,
  132. },
  133. {
  134. text: `V2(m/s):`,
  135. font: 'normal 28px Arial',
  136. color: '#009900',
  137. strokeStyle: '#002200',
  138. x: 330,
  139. y: 182,
  140. },
  141. {
  142. text: `${selectData.incipientWindSpeed2 ? selectData.incipientWindSpeed2 : '-'}`,
  143. font: 'normal 28px Arial',
  144. color: '#009900',
  145. strokeStyle: '#002200',
  146. x: 452,
  147. y: 182,
  148. },
  149. {
  150. text: `V3(m/s):`,
  151. font: 'normal 28px Arial',
  152. color: '#009900',
  153. strokeStyle: '#002200',
  154. x: 330,
  155. y: 245,
  156. },
  157. {
  158. text: `${selectData.incipientWindSpeed3 ? selectData.incipientWindSpeed3 : '-'}`,
  159. font: 'normal 28px Arial',
  160. color: '#009900',
  161. strokeStyle: '#002200',
  162. x: 452,
  163. y: 245,
  164. },
  165. {
  166. text: `煤炭科学技术研究院有限公司研制`,
  167. font: 'normal 28px Arial',
  168. color: '#009900',
  169. strokeStyle: '#002200',
  170. x: 60,
  171. y: 302,
  172. },
  173. ];
  174. getTextCanvas(560, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
  175. const textMap = new THREE.CanvasTexture(canvas); // 关键一步
  176. const textMaterial = new THREE.MeshBasicMaterial({
  177. map: textMap, // 设置纹理贴图
  178. transparent: true,
  179. side: THREE.DoubleSide, // 这里是双面渲染的意思
  180. });
  181. textMaterial.blending = THREE.CustomBlending;
  182. const monitorPlane = this.group?.getObjectByName('monitorText');
  183. if (monitorPlane) {
  184. monitorPlane.material = textMaterial;
  185. } else {
  186. const planeGeometry = new THREE.PlaneGeometry(560, 346); // 平面3维几何体PlaneGeometry
  187. const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
  188. planeMesh.name = 'monitorText';
  189. planeMesh.scale.set(0.0022, 0.0022, 0.0022);
  190. planeMesh.position.set(3.25, -0.002, -0.41);
  191. this.group?.add(planeMesh);
  192. }
  193. });
  194. }
  195. /* 风门动画 */
  196. render() {
  197. if (!this.model) {
  198. return;
  199. }
  200. if (this.isLRAnimation && this.group) {
  201. // 左右摇摆动画
  202. if (Math.abs(this.group.rotation.y) >= 0.2) {
  203. this.direction = -this.direction;
  204. this.group.rotation.y += 0.00002 * 30 * this.direction;
  205. } else {
  206. this.group.rotation.y += 0.00002 * 30 * this.direction;
  207. }
  208. }
  209. }
  210. /* 提取风门序列帧,初始化前后门动画 */
  211. initAnimation() {
  212. const windGroup = new THREE.Group();
  213. windGroup.name = 'lmTanTou';
  214. if (this.group?.children.length) {
  215. for (let i = this.group?.children.length - 1; i > -1; i--) {
  216. const obj = this.group?.children[i];
  217. if (obj.type === 'Mesh' && obj.name && obj.name.startsWith('LMtantou')) {
  218. if (obj.name.startsWith('LMtantou')) {
  219. windGroup.add(obj.clone());
  220. this.group?.remove(obj);
  221. }
  222. }
  223. }
  224. }
  225. this.group?.add(windGroup);
  226. }
  227. /* 点击风窗,风窗全屏 */
  228. mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
  229. this.isLRAnimation = false;
  230. if (this.animationTimer) {
  231. clearTimeout(this.animationTimer);
  232. this.animationTimer = null;
  233. }
  234. // 判断是否点击到视频
  235. intersects.find((intersect) => {
  236. const mesh = intersect.object;
  237. if (mesh.name === 'player1') {
  238. if (new Date().getTime() - this.playerStartClickTime1 < 400) {
  239. // 双击,视频放大
  240. if (this.player1) {
  241. this.player1.requestFullscreen();
  242. }
  243. }
  244. this.playerStartClickTime1 = new Date().getTime();
  245. return true;
  246. } else if (mesh.name === 'player2') {
  247. if (new Date().getTime() - this.playerStartClickTime2 < 400) {
  248. // 双击,视频放大
  249. if (this.player2) {
  250. this.player2.requestFullscreen();
  251. }
  252. }
  253. this.playerStartClickTime2 = new Date().getTime();
  254. return true;
  255. }
  256. return false;
  257. });
  258. }
  259. mouseUpModel() {
  260. // 10s后开始摆动
  261. if (!this.animationTimer && !this.isLRAnimation) {
  262. this.animationTimer = setTimeout(() => {
  263. this.isLRAnimation = true;
  264. }, 10000);
  265. }
  266. }
  267. resetModel() {
  268. clearTimeout(this.animationTimer);
  269. this.isLRAnimation = false;
  270. }
  271. // 播放动画
  272. play(flag, isDirect = false) {
  273. const cfTanTou = this.group?.getObjectByName('lmTanTou') as THREE.Group;
  274. if (!cfTanTou) return;
  275. switch (flag) {
  276. case 'up':
  277. if (this.deviceRunState == 'up' || this.deviceRunState == 'center') {
  278. return;
  279. }
  280. if (!isDirect) {
  281. this.deviceRunState = 'up';
  282. if (this.deviceRunState == 'down') cfTanTou.position.setY(-0.48);
  283. gsap.to(cfTanTou['position'], {
  284. id: 'lm',
  285. y: 0,
  286. duration: Math.abs(cfTanTou['position']['y'] - 0) * 25,
  287. overwrite: true,
  288. });
  289. } else {
  290. cfTanTou.position.setY(0);
  291. }
  292. break;
  293. case 'center':
  294. if (this.deviceRunState) {
  295. return;
  296. }
  297. if (!isDirect) {
  298. this.deviceRunState = 'center';
  299. cfTanTou.position.setY(0);
  300. gsap.to(cfTanTou['position'], {
  301. id: 'lm',
  302. y: -0.24,
  303. duration: Math.abs(cfTanTou['position']['y'] + 0.24) * 50,
  304. overwrite: true,
  305. });
  306. } else {
  307. this.deviceRunState = 'center';
  308. cfTanTou.position.setY(-0.24);
  309. }
  310. break;
  311. case 'down':
  312. // debugger;
  313. if (this.deviceRunState == 'down' || this.deviceRunState == 'up') {
  314. return;
  315. }
  316. if (!isDirect) {
  317. if (this.deviceRunState == 'center') {
  318. cfTanTou.position.setY(-0.24);
  319. }
  320. this.deviceRunState = 'down';
  321. gsap.to(cfTanTou['position'], {
  322. id: 'lm',
  323. y: -0.48,
  324. duration: Math.abs(cfTanTou['position']['y'] + 0.48) * 50,
  325. overwrite: true,
  326. });
  327. } else {
  328. this.deviceRunState = 'down';
  329. cfTanTou.position.setY(-0.48);
  330. }
  331. break;
  332. }
  333. }
  334. mountedThree() {
  335. return new Promise((resolve) => {
  336. this.model.setGLTFModel([this.modelName]).then((gltf) => {
  337. this.group = gltf[0];
  338. this.setModalPosition();
  339. this.initAnimation();
  340. this.addLight();
  341. setTimeout(async () => {
  342. const videoPlayer1 = document.getElementById('cf-player1')?.getElementsByClassName('vjs-tech')[0];
  343. if (videoPlayer1) {
  344. const mesh = renderVideo(this.group, videoPlayer1, 'player1');
  345. mesh?.scale.set(0.042, 0.036, 0.022);
  346. mesh?.position.set(-2.74, 0.03, -0.39);
  347. this.group?.add(mesh as THREE.Mesh);
  348. } else {
  349. // 视频无信号
  350. const textArr = [
  351. {
  352. text: `无信号输入`,
  353. font: 'normal 40px Arial',
  354. color: '#009900',
  355. strokeStyle: '#002200',
  356. x: 170,
  357. y: 40,
  358. },
  359. ];
  360. getTextCanvas(560, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
  361. const textMap = new THREE.CanvasTexture(canvas); // 关键一步
  362. const textMaterial = new THREE.MeshBasicMaterial({
  363. map: textMap, // 设置纹理贴图
  364. transparent: true,
  365. side: THREE.DoubleSide, // 这里是双面渲染的意思
  366. });
  367. textMaterial.blending = THREE.CustomBlending;
  368. const monitorPlane = this.group?.getObjectByName('noPlayer');
  369. if (monitorPlane) {
  370. monitorPlane.material = textMaterial;
  371. } else {
  372. const planeGeometry = new THREE.PlaneGeometry(100, 100); // 平面3维几何体PlaneGeometry
  373. const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
  374. planeMesh.name = 'noPlayer';
  375. planeMesh.scale.set(0.012, 0.009, 0.012);
  376. planeMesh.position.set(-2.72, -0.34, -0.39);
  377. this.group?.add(planeMesh);
  378. }
  379. });
  380. }
  381. resolve(null);
  382. }, 0);
  383. });
  384. });
  385. }
  386. destroy() {
  387. if (this.group) {
  388. this.model.clearGroup(this.group);
  389. }
  390. this.model = null;
  391. this.group = null;
  392. }
  393. }
  394. export default lmWindRect;