dandaoFcBd3.threejs.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. import * as THREE from 'three';
  2. import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
  3. import gsap from 'gsap';
  4. class ddFc_7 {
  5. model;
  6. modelName = 'ddFcGroup';
  7. group: THREE.Object3D = new THREE.Object3D();
  8. animationTimer;
  9. isLRAnimation = true;
  10. direction = 1;
  11. windowsActionArr = {
  12. frontWindow: [],
  13. backWindow: [],
  14. };
  15. player1;
  16. player2;
  17. playerStartClickTime1 = new Date().getTime();
  18. mixers: THREE.AnimationMixer | undefined;
  19. frontClipAction;
  20. constructor(model) {
  21. this.model = model;
  22. this.group.name = 'ddFcGroup';
  23. }
  24. addLight = () => {
  25. if (!this.group || !this.group) return;
  26. const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
  27. directionalLight.position.set(-437, 61, 559);
  28. this.group.add(directionalLight);
  29. const pointLight2 = new THREE.PointLight(0xffffff, 1, 150);
  30. pointLight2.position.set(-101, 34, 16);
  31. pointLight2.shadow.bias = 0.05;
  32. this.group.add(pointLight2);
  33. const pointLight3 = new THREE.PointLight(0xffffff, 1, 150);
  34. pointLight3.position.set(19, 25, -7);
  35. pointLight3.shadow.bias = 0.05;
  36. this.group.add(pointLight3);
  37. const pointLight6 = new THREE.PointLight(0xffffff, 1, 300);
  38. pointLight6.position.set(51, 51, 9);
  39. pointLight6.shadow.bias = 0.05;
  40. this.group.add(pointLight6);
  41. };
  42. // 设置模型位置
  43. setModalPosition() {
  44. this.group?.scale.set(22, 22, 22);
  45. this.group?.position.set(-35, 25, 15);
  46. }
  47. addMonitorText(selectData) {
  48. if (!this.group) {
  49. return;
  50. }
  51. const screenDownText = VENT_PARAM['modalText']
  52. ? VENT_PARAM['modalText']
  53. : History_Type['type'] == 'remote'
  54. ? `国能神东煤炭集团监制`
  55. : '煤科通安(北京)智控科技有限公司研制';
  56. const screenDownTextX = 90 - (screenDownText.length - 10) * 7;
  57. const textArr = [
  58. {
  59. text: `远程定量调节自动风窗`,
  60. font: 'normal 30px Arial',
  61. color: '#009900',
  62. strokeStyle: '#002200',
  63. x: 100,
  64. y: 95,
  65. },
  66. {
  67. text: `${selectData.OpenDegree ? '开度值(°)' : selectData.frontArea ? '过风面积(㎡)' : '过风面积(㎡)'}:`,
  68. font: 'normal 30px Arial',
  69. color: '#009900',
  70. strokeStyle: '#002200',
  71. x: 5,
  72. y: 145,
  73. },
  74. {
  75. text: selectData.OpenDegree
  76. ? Number(`${selectData.OpenDegree}`).toFixed(2)
  77. : selectData.frontArea
  78. ? Number(`${selectData.frontArea}`).toFixed(2)
  79. : '-',
  80. font: 'normal 30px Arial',
  81. color: '#009900',
  82. strokeStyle: '#002200',
  83. x: 330,
  84. y: 145,
  85. },
  86. {
  87. text: `${selectData.frontRearDP ? '风窗压差(Pa)' : selectData.windSpeed ? '风速(m/s)' : '通信状态:'}:`,
  88. font: 'normal 30px Arial',
  89. color: '#009900',
  90. strokeStyle: '#002200',
  91. x: 5,
  92. y: 200,
  93. },
  94. {
  95. text: `${
  96. selectData.frontRearDP
  97. ? selectData.frontRearDP
  98. : selectData.windSpeed
  99. ? selectData.windSpeed
  100. : selectData.netStatus == '0'
  101. ? '断开'
  102. : '连接'
  103. }`,
  104. font: 'normal 30px Arial',
  105. color: '#009900',
  106. strokeStyle: '#002200',
  107. x: 330,
  108. y: 200,
  109. },
  110. {
  111. text: `${selectData.fWindowM3 ? '过风量(m³/min)' : '风窗道数'}: `,
  112. font: 'normal 30px Arial',
  113. color: '#009900',
  114. strokeStyle: '#002200',
  115. x: 5,
  116. y: 250,
  117. },
  118. {
  119. text: `${selectData.fWindowM3 ? selectData.fWindowM3 : selectData.nwindownum}`,
  120. font: 'normal 30px Arial',
  121. color: '#009900',
  122. strokeStyle: '#002200',
  123. x: 330,
  124. y: 250,
  125. },
  126. {
  127. text: screenDownText,
  128. font: 'normal 28px Arial',
  129. color: '#009900',
  130. strokeStyle: '#002200',
  131. x: screenDownTextX,
  132. y: 300,
  133. },
  134. ];
  135. getTextCanvas(750, 546, textArr, '').then((canvas: HTMLCanvasElement) => {
  136. const textMap = new THREE.CanvasTexture(canvas); // 关键一步
  137. const textMaterial = new THREE.MeshBasicMaterial({
  138. // 关于材质并未讲解 实操即可熟悉 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
  139. map: textMap, // 设置纹理贴图
  140. transparent: true,
  141. side: THREE.DoubleSide, // 这里是双面渲染的意思
  142. });
  143. textMap.dispose();
  144. textMaterial.blending = THREE.CustomBlending;
  145. const monitorPlane = this.group?.getObjectByName('monitorText');
  146. if (monitorPlane) {
  147. monitorPlane.material = textMaterial;
  148. } else {
  149. const planeGeometry = new THREE.PlaneGeometry(526, 346); // 平面3维几何体PlaneGeometry
  150. const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
  151. planeMesh.name = 'monitorText';
  152. planeMesh.scale.set(0.003, 0.0034, 0.004);
  153. planeMesh.position.set(4.25, 0.41, -0.27);
  154. this.group?.add(planeMesh);
  155. }
  156. });
  157. }
  158. /* 提取风门序列帧,初始化前后门动画 */
  159. initAnimation() {
  160. debugger;
  161. const modalGroup = this.group?.getObjectByName('ddFc-bd3');
  162. // 初始化窗得动画
  163. const meshArr01: THREE.Object3D[] = [];
  164. const meshArr02: THREE.Object3D[] = [];
  165. const fmGroup = modalGroup?.getObjectByName('FengMen_ShouDong_1')?.getObjectByName('FengMen_1');
  166. const fcGroup1 = fmGroup?.getObjectByName('Men_1');
  167. const fcGroup2 = fmGroup?.getObjectByName('Men_2');
  168. if (fcGroup1 && fcGroup2) {
  169. fcGroup1.getObjectByName('shanye_1')?.children.filter((obj) => {
  170. if (obj.name.includes('shanye_1')) {
  171. obj.rotateOnAxis(new THREE.Vector3(0, 1, 0), 0);
  172. meshArr01.push(obj);
  173. }
  174. });
  175. fcGroup1.getObjectByName('shanye_2')?.children.filter((obj) => {
  176. if (obj.name.includes('shanye_2')) {
  177. obj.rotateOnAxis(new THREE.Vector3(0, 1, 0), 0);
  178. meshArr01.push(obj);
  179. }
  180. });
  181. fcGroup2.children.filter((obj) => {
  182. if (obj.name.includes('shanye_3')) {
  183. obj.rotateOnAxis(new THREE.Vector3(0, 1, 0), 0);
  184. meshArr02.push(obj);
  185. }
  186. });
  187. fcGroup2.getObjectByName('shanye_4')?.children.filter((obj) => {
  188. if (obj.name.includes('shanye_4')) {
  189. obj.rotateOnAxis(new THREE.Vector3(0, 1, 0), 0);
  190. meshArr02.push(obj);
  191. }
  192. });
  193. this.windowsActionArr.frontWindow = [...meshArr01];
  194. this.windowsActionArr.backWindow = [...meshArr02];
  195. }
  196. // 初始化门的动画
  197. if (modalGroup && fmGroup) {
  198. const tracks = modalGroup.animations[0].tracks;
  199. const fontTracks: any[] = [];
  200. for (let i = 0; i < tracks.length; i++) {
  201. const track = tracks[i];
  202. if (track.name.startsWith('Men')) {
  203. fontTracks.push(track);
  204. }
  205. }
  206. this.mixers = new THREE.AnimationMixer(fmGroup);
  207. const frontDoor = new THREE.AnimationClip('frontDoor', 15, fontTracks);
  208. this.frontClipAction = this.mixers.clipAction(frontDoor, fmGroup);
  209. this.frontClipAction.clampWhenFinished = true;
  210. this.frontClipAction.reset();
  211. this.frontClipAction.time = 0;
  212. this.frontClipAction.timeScale = 1;
  213. this.frontClipAction.clampWhenFinished = true;
  214. this.frontClipAction.loop = THREE.LoopOnce;
  215. this.frontClipAction.play();
  216. }
  217. }
  218. play(rotationParam, flag) {
  219. if (!this.windowsActionArr.frontWindow) {
  220. return;
  221. }
  222. if (flag === 1) {
  223. // 前风窗动画
  224. this.windowsActionArr.frontWindow.forEach((mesh: THREE.Mesh) => {
  225. gsap.to(mesh.rotation, {
  226. z: THREE.MathUtils.degToRad(rotationParam.frontDeg1),
  227. duration: (1 / 9) * Math.abs(rotationParam.frontDeg1 - mesh.rotation.z),
  228. overwrite: true,
  229. });
  230. });
  231. } else if (flag === 2) {
  232. // 后风窗动画
  233. this.windowsActionArr.backWindow.forEach((mesh: THREE.Mesh) => {
  234. gsap.to(mesh.rotation, {
  235. z: THREE.MathUtils.degToRad(rotationParam.backDeg1),
  236. duration: (1 / 9) * Math.abs(rotationParam.backDeg1 - mesh.rotation.z),
  237. overwrite: true,
  238. });
  239. });
  240. } else if (flag === 0) {
  241. ([...this.windowsActionArr.frontWindow] as THREE.Mesh[]).forEach((mesh) => {
  242. gsap.to(mesh.rotation, {
  243. z: THREE.MathUtils.degToRad(90),
  244. overwrite: true,
  245. });
  246. });
  247. }
  248. }
  249. /* 点击风窗,风窗全屏 */
  250. mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
  251. if (this.animationTimer) {
  252. clearTimeout(this.animationTimer);
  253. this.animationTimer = null;
  254. }
  255. // 判断是否点击到视频
  256. intersects.find((intersect) => {
  257. const mesh = intersect.object;
  258. if (mesh.name === 'player1') {
  259. if (new Date().getTime() - this.playerStartClickTime1 < 400) {
  260. // 双击,视频放大
  261. if (this.player1) {
  262. this.player1.requestFullscreen();
  263. }
  264. }
  265. this.playerStartClickTime1 = new Date().getTime();
  266. return true;
  267. }
  268. return false;
  269. });
  270. }
  271. mouseUpModel() {}
  272. /* 风门动画 */
  273. render() {
  274. if (!this.model) {
  275. return;
  276. }
  277. if (this.mixers) {
  278. this.mixers.update(2);
  279. }
  280. }
  281. mountedThree() {
  282. return new Promise((resolve) => {
  283. this.model.setGLTFModel(['ddFc-bd3'], this.group).then(() => {
  284. console.log(this.group);
  285. this.setModalPosition();
  286. this.initAnimation();
  287. resolve(null);
  288. });
  289. });
  290. }
  291. destroy() {
  292. if (this.mixers) {
  293. const modalGroup = this.group?.getObjectByName('ddFc-bd3');
  294. const fmGroup = modalGroup?.getObjectByName('FengMen_ShouDong_1')?.getObjectByName('FengMen_1');
  295. this.mixers.uncacheClip(this.frontClipAction.getClip());
  296. this.mixers.uncacheAction(this.frontClipAction.getClip(), fmGroup);
  297. if (fmGroup) this.mixers.uncacheRoot(fmGroup);
  298. if (this.model.animations[0]) this.model.animations[0].tracks = [];
  299. }
  300. this.model.clearGroup(this.group);
  301. this.windowsActionArr.frontWindow = undefined;
  302. this.windowsActionArr.backWindow = undefined;
  303. this.model = null;
  304. this.group = null;
  305. }
  306. }
  307. export default ddFc_7;