duishe.threejs.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. import * as THREE from 'three';
  2. import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
  3. import gsap from 'gsap';
  4. import * as dat from 'dat.gui';
  5. const gui = new dat.GUI();
  6. class dsWindRect {
  7. model;
  8. modelName = 'dscf';
  9. group: THREE.Group | null = null;
  10. animationTimer;
  11. isLRAnimation = true;
  12. direction = 1;
  13. player1;
  14. playerStartClickTime1 = new Date().getTime();
  15. lineLight;
  16. constructor(model, playerVal1) {
  17. this.model = model;
  18. this.player1 = playerVal1;
  19. }
  20. addLight() {
  21. // const pointLight2 = new THREE.PointLight(0xffeeee, 1, 0);
  22. // pointLight2.position.set(317, 41, -21);
  23. // pointLight2.shadow.bias = 0.05;
  24. // this.lightGroup.add(pointLight2);
  25. // //
  26. // const pointLight3 = new THREE.PointLight(0xffffff, 1, 138);
  27. // pointLight3.position.set(-202, 50, 27.8);
  28. // pointLight3.shadow.bias = 0.05;
  29. // this.lightGroup.add(pointLight3);
  30. // //
  31. // const pointLight4 = new THREE.PointLight(0xffeeee, 1, 162);
  32. // pointLight4.position.set(83, 62, -14);
  33. // pointLight4.shadow.bias = 0.05;
  34. // this.lightGroup.add(pointLight4);
  35. // //
  36. // const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 176);
  37. // pointLight5.position.set(-123, 61.6, -18);
  38. // pointLight5.shadow.bias = 0.05;
  39. // this.lightGroup.add(pointLight5);
  40. // //
  41. // const pointLight6 = new THREE.PointLight(0xffffff, 1.5, 64);
  42. // pointLight6.position.set(-76, 48, 18);
  43. // pointLight6.shadow.bias = 0.05;
  44. // this.lightGroup.add(pointLight6);
  45. // const pointLight7 = new THREE.PointLight(0xffffff, 1, 302);
  46. // pointLight7.position.set(-220, -177, -3.5);
  47. // pointLight7.shadow.bias = -0.05;
  48. // this.lightGroup.add(pointLight7);
  49. const spotLight = new THREE.SpotLight();
  50. spotLight.angle = Math.PI / 16;
  51. spotLight.penumbra = 0;
  52. spotLight.castShadow = true;
  53. spotLight.distance = 0;
  54. spotLight.position.set(-470, -500, 500);
  55. this.model.add(spotLight);
  56. spotLight.shadow.camera.near = 0.5; // default
  57. spotLight.shadow.camera.far = 1000; // default
  58. spotLight.shadow.focus = 1;
  59. spotLight.shadow.bias = -0.000002;
  60. this.model.scene.add(this.model);
  61. gui.add(spotLight.position, 'x', -500, 500);
  62. gui.add(spotLight.position, 'y', -500, 500);
  63. gui.add(spotLight.position, 'z', -500, 500);
  64. gui.add(spotLight, 'distance', 0, 1000);
  65. }
  66. // 设置模型位置
  67. setModalPosition() {
  68. this.group?.scale.set(27, 27, 27);
  69. this.group?.position.set(-10, 25, 20);
  70. }
  71. addFmText(selectData) {
  72. if (!this.group) {
  73. return;
  74. }
  75. const textArr = [
  76. {
  77. text: `煤矿巷道远程风窗系统`,
  78. font: 'normal 2.2rem Arial',
  79. color: '#009900',
  80. strokeStyle: '#002200',
  81. x: 90,
  82. y: 95,
  83. },
  84. {
  85. text: `过风量(m3/min):`,
  86. font: 'normal 30px Arial',
  87. color: '#009900',
  88. strokeStyle: '#002200',
  89. x: 5,
  90. y: 115,
  91. },
  92. {
  93. text: `${
  94. selectData.frontRearDifference && selectData.rearPresentValue
  95. ? Math.min(selectData.frontRearDifference, selectData.rearPresentValue)
  96. : selectData.frontRearDifference || selectData.rearPresentValue || '-'
  97. }`,
  98. font: 'normal 30px Arial',
  99. color: '#009900',
  100. strokeStyle: '#002200',
  101. x: 235,
  102. y: 115,
  103. },
  104. {
  105. text: `过风面积(m2): `,
  106. font: 'normal 30px Arial',
  107. color: '#009900',
  108. strokeStyle: '#002200',
  109. x: 5,
  110. y: 182,
  111. },
  112. {
  113. text: `${
  114. selectData.forntArea && selectData.rearArea
  115. ? Math.min(selectData.forntArea, selectData.rearArea)
  116. : selectData.forntArea || selectData.rearArea || '-'
  117. }`,
  118. font: 'normal 30px Arial',
  119. color: '#009900',
  120. strokeStyle: '#002200',
  121. x: 200,
  122. y: 182,
  123. },
  124. {
  125. text: `风窗压差(Pa):`,
  126. font: 'normal 30px Arial',
  127. color: '#009900',
  128. strokeStyle: '#002200',
  129. x: 5,
  130. y: 245,
  131. },
  132. {
  133. text: `${selectData.dataDh}`,
  134. font: 'normal 30px Arial',
  135. color: '#009900',
  136. strokeStyle: '#002200',
  137. x: 200,
  138. y: 245,
  139. },
  140. {
  141. text: `调节精度:`,
  142. font: 'normal 30px Arial',
  143. color: '#009900',
  144. strokeStyle: '#002200',
  145. x: 320,
  146. y: 115,
  147. },
  148. {
  149. text: `1% FS`,
  150. font: 'normal 30px Arial',
  151. color: '#009900',
  152. strokeStyle: '#002200',
  153. x: 460,
  154. y: 115,
  155. },
  156. {
  157. text: `调节范围:`,
  158. font: 'normal 30px Arial',
  159. color: '#009900',
  160. strokeStyle: '#002200',
  161. x: 320,
  162. y: 182,
  163. },
  164. {
  165. text: `${selectData.maxarea}`,
  166. font: 'normal 30px Arial',
  167. color: '#009900',
  168. strokeStyle: '#002200',
  169. x: 460,
  170. y: 182,
  171. },
  172. {
  173. text: `煤炭科学技术研究院有限公司研制`,
  174. font: 'normal 28px Arial',
  175. color: '#009900',
  176. strokeStyle: '#002200',
  177. x: 60,
  178. y: 302,
  179. },
  180. ];
  181. getTextCanvas(560, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
  182. const textMap = new THREE.CanvasTexture(canvas); // 关键一步
  183. const textMaterial = new THREE.MeshBasicMaterial({
  184. map: textMap, // 设置纹理贴图
  185. transparent: true,
  186. side: THREE.DoubleSide, // 这里是双面渲染的意思
  187. });
  188. textMaterial.blending = THREE.CustomBlending;
  189. const monitorPlane = this.group?.getObjectByName('monitorText');
  190. if (monitorPlane) {
  191. monitorPlane.material = textMaterial;
  192. } else {
  193. const planeGeometry = new THREE.PlaneGeometry(5.6, 3.46); // 平面3维几何体PlaneGeometry
  194. const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
  195. planeMesh.name = 'monitorText';
  196. planeMesh.scale.set(0.0022, 0.0022, 0.0022);
  197. planeMesh.position.set(3.25, -0.002, -0.41);
  198. this.group?.add(planeMesh);
  199. }
  200. });
  201. }
  202. /* 风门动画 */
  203. render() {
  204. if (!this.model) {
  205. return;
  206. }
  207. if (this.isLRAnimation && this.group) {
  208. // 左右摇摆动画
  209. if (Math.abs(this.group.rotation.y) >= 0.2) {
  210. this.direction = -this.direction;
  211. this.group.rotation.y += 0.00005 * 30 * this.direction;
  212. } else {
  213. this.group.rotation.y += 0.00005 * 30 * this.direction;
  214. }
  215. }
  216. }
  217. /* 提取风门序列帧,初始化前后门动画 */
  218. initAnimation() {
  219. const windGroup = new THREE.Group();
  220. windGroup.name = 'dsTanTou';
  221. let line;
  222. if (this.group && this.group?.children.length > 0) {
  223. for (let i = this.group?.children.length - 1; i >= 0; i--) {
  224. const obj = this.group?.children[i];
  225. if (obj.type === 'Mesh' && obj.name && obj.name.includes('Tantou')) {
  226. const mesh = obj.clone();
  227. if (mesh.name === 'Tantouxian') {
  228. line = mesh;
  229. }
  230. windGroup.add(mesh);
  231. obj.removeFromParent();
  232. this.group?.remove(obj);
  233. }
  234. }
  235. }
  236. this.group?.add(windGroup);
  237. if (line) {
  238. line.material.side = THREE.DoubleSide;
  239. line.material.depthWrite = true;
  240. this.lineLight = gsap.to(line.material, {
  241. opacity: 0,
  242. duration: 0.3,
  243. ease: 'easeQutQuad',
  244. repeat: -1,
  245. yoyo: true,
  246. paused: true,
  247. });
  248. this.lineLight.play();
  249. }
  250. }
  251. /* 点击风窗,风窗全屏 */
  252. mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
  253. this.isLRAnimation = false;
  254. if (this.animationTimer) {
  255. clearTimeout(this.animationTimer);
  256. this.animationTimer = null;
  257. }
  258. // 判断是否点击到视频
  259. intersects.find((intersect) => {
  260. const mesh = intersect.object;
  261. if (mesh.name === 'player1') {
  262. if (new Date().getTime() - this.playerStartClickTime1 < 400) {
  263. // 双击,视频放大
  264. if (this.player1) {
  265. this.player1.requestFullscreen();
  266. }
  267. }
  268. this.playerStartClickTime1 = new Date().getTime();
  269. return true;
  270. }
  271. return false;
  272. });
  273. }
  274. mouseUpModel() {
  275. // 10s后开始摆动
  276. if (!this.animationTimer && !this.isLRAnimation) {
  277. this.animationTimer = setTimeout(() => {
  278. this.isLRAnimation = true;
  279. }, 10000);
  280. }
  281. }
  282. resetModel() {
  283. clearTimeout(this.animationTimer);
  284. this.isLRAnimation = false;
  285. }
  286. // 播放动画
  287. play(flag) {
  288. const dsTanTou = this.group?.getObjectByName('dsTanTou') as THREE.Group;
  289. if (!dsTanTou) return;
  290. switch (flag) {
  291. case 'up':
  292. gsap.to(dsTanTou['position'], {
  293. y: 0,
  294. duration: Math.abs(dsTanTou['position']['y']) * 5,
  295. ease: 'easeQutQuad',
  296. overwrite: true,
  297. });
  298. break;
  299. case 'center':
  300. gsap.to(dsTanTou['position'], {
  301. y: -0.345,
  302. duration: Math.abs(dsTanTou['position']['y'] - 0.345) * 5,
  303. ease: 'easeQutQuad',
  304. overwrite: true,
  305. });
  306. break;
  307. case 'down':
  308. gsap.to(dsTanTou['position'], {
  309. y: -0.69,
  310. duration: Math.abs(dsTanTou['position']['y'] - 0.69) * 5,
  311. ease: 'easeQutCubic',
  312. overwrite: true,
  313. });
  314. break;
  315. }
  316. }
  317. mountedThree() {
  318. return new Promise((resolve) => {
  319. this.model.setModel(this.modelName).then((gltf) => {
  320. this.group = gltf.scene;
  321. this.setModalPosition();
  322. this.initAnimation();
  323. setTimeout(async () => {
  324. const videoPlayer1 = document.getElementById('cf-player1')?.getElementsByClassName('vjs-tech')[0];
  325. if (videoPlayer1) {
  326. const mesh = renderVideo(this.group, videoPlayer1, 'player1');
  327. mesh?.scale.set(0.042, 0.036, 1);
  328. mesh?.position.set(2.7, 0.016, -0.38);
  329. this.group?.add(mesh as THREE.Mesh);
  330. }
  331. resolve(null);
  332. }, 0);
  333. });
  334. });
  335. }
  336. destroy() {
  337. if (this.group) {
  338. this.model.clearGroup(this.group);
  339. }
  340. this.model = null;
  341. this.group = null;
  342. }
  343. }
  344. export default dsWindRect;