gasAssessmen.threejs.base.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. import * as THREE from 'three';
  2. import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
  3. import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
  4. import { setModalCenter, setTag3D, gradientColors } from '/@/utils/threejs/util';
  5. import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
  6. import { green, yellow } from '@ant-design/colors';
  7. // import * as dat from 'dat.gui';
  8. // const gui = new dat.GUI();
  9. // gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
  10. class GasAssessmen {
  11. model;
  12. modelName = 'workFace';
  13. group: THREE.Object3D = new THREE.Object3D();
  14. planeGroup: THREE.Group = new THREE.Group();
  15. bloomComposer: EffectComposer | null = null;
  16. finalComposer: EffectComposer | null = null;
  17. outlinePass: OutlinePass | null = null;
  18. positions: THREE.Vector3[][] = [];
  19. bloomLayer = new THREE.Layers();
  20. darkMaterial = new THREE.MeshBasicMaterial({ color: 'black', transparent: true, side: THREE.DoubleSide });
  21. materials = {};
  22. glob = {
  23. ENTIRE_SCENE: 0,
  24. BLOOM_SCENE: 10,
  25. N: 100,
  26. };
  27. locationTexture: THREE.Texture | null = null;
  28. warningLocationTexture: THREE.Texture | null = null;
  29. errorLocationTexture: THREE.Texture | null = null;
  30. playerStartClickTime1 = new Date().getTime();
  31. playerStartClickTime2 = new Date().getTime();
  32. planeNum = 0;
  33. unitList = [];
  34. constructor(model) {
  35. this.model = model;
  36. this.group.name = this.modelName;
  37. }
  38. addLight() {
  39. // const _this = this;
  40. const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
  41. directionalLight.position.set(-196, 150, 258);
  42. this.group.add(directionalLight);
  43. directionalLight.target = this.group;
  44. }
  45. render() {
  46. this.model.renderer?.render(this.model.scene as THREE.Scene, this.model.camera as THREE.PerspectiveCamera);
  47. }
  48. // 绘制抽采单元
  49. setPlanes1 = (n, colors = new Array(n).fill(new THREE.Color('rgb(100%, 0%, 0%)'))) => {
  50. const sizeList = [0.2, 0.3, 0.1, 0.4];
  51. // width = 7.713 height =3.717
  52. colors = gradientColors('#00FF2C', '#FF0000', n, 2);
  53. this.planeNum = n;
  54. const lenScale = 0.77 / n;
  55. const planeGeo = new THREE.PlaneGeometry();
  56. planeGeo.applyMatrix4(new THREE.Matrix4().makeTranslation(-1, 0, 0));
  57. for (let i = 0; i < n; i++) {
  58. const material = new THREE.MeshBasicMaterial({ color: colors[i], transparent: true, opacity: 0.6, depthTest: false, depthWrite: false });
  59. const plane = new THREE.Mesh(planeGeo, material);
  60. plane.name = 'unit' + i;
  61. plane.rotation.x = -Math.PI / 2;
  62. plane.scale.set(lenScale - 0.001, 0.375, 1.0);
  63. plane.position.set(0.282 - lenScale * (i - 0.5), 0.015, 0.142);
  64. this.planeGroup.add(plane);
  65. }
  66. this.group.add(this.planeGroup);
  67. };
  68. setPlanes = (n) => {
  69. // const sizeList = [0.2, 0.3, 0.1, 0.2, 0.2];
  70. const colors = {
  71. c1: new THREE.Color(0x00fe00), // >90
  72. c2: new THREE.Color(0xf9b866), // >75 <90 249,184,102
  73. c3: new THREE.Color(0xfefe00), // 50-75 254,254,0
  74. c4: new THREE.Color(0xfe6600), // 25-50 254,102,0
  75. c5: new THREE.Color(0xb00101), // <25 176,1,1
  76. };
  77. const sizeList = [
  78. {
  79. id: '',
  80. ratio: 0.2,
  81. color: colors.c1,
  82. },
  83. {
  84. id: '',
  85. ratio: 0.3,
  86. color: colors.c2,
  87. },
  88. {
  89. id: '',
  90. ratio: 0.1,
  91. color: colors.c4,
  92. },
  93. {
  94. id: '',
  95. ratio: 0.2,
  96. color: colors.c5,
  97. },
  98. {
  99. id: '',
  100. ratio: 0.2,
  101. color: colors.c3,
  102. },
  103. ];
  104. this.unitList = sizeList;
  105. // width = 7.713 height =3.717
  106. // const sizeList = [
  107. // {
  108. // ratio: 0.4,
  109. // color: colors.c1,
  110. // },
  111. // {
  112. // ratio: 0.5,
  113. // color: colors.c2,
  114. // },
  115. // {
  116. // ratio: 0.1,
  117. // color: colors.c4,
  118. // },
  119. // ];
  120. const geometry = new THREE.PlaneGeometry(7.723, 3.72, 1, 1);
  121. // 初始化累积比例数组和颜色数组
  122. const accumulatedRatios = [];
  123. const colorsArray = new Float32Array(3 * sizeList.length);
  124. // 计算累积比例和颜色数组
  125. function updateShaderData(sizeList) {
  126. let accRatio = 0;
  127. for (let i = 0; i < sizeList.length; i++) {
  128. const item = sizeList[i];
  129. accRatio += item.ratio;
  130. accumulatedRatios.push(accRatio);
  131. colorsArray[i * 3] = item.color.r;
  132. colorsArray[i * 3 + 1] = item.color.g;
  133. colorsArray[i * 3 + 2] = item.color.b;
  134. }
  135. }
  136. updateShaderData(sizeList); // 初始调用
  137. // 定义着色器代码
  138. const vertexShader = `
  139. varying vec2 vUv;
  140. void main() {
  141. vUv = uv;
  142. gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  143. }
  144. `;
  145. const fragmentShader = `
  146. varying vec2 vUv;
  147. uniform float ratios[${sizeList.length}];
  148. uniform vec3 colors[${sizeList.length}];
  149. void main() {
  150. for(int i = 0; i < ${sizeList.length}; i++) {
  151. if(vUv.x < ratios[i]) {
  152. gl_FragColor = vec4(colors[i], 1.0);
  153. return;
  154. }
  155. }
  156. gl_FragColor = vec4(0.0, 0.0, 0.0, 0.5);
  157. }
  158. `;
  159. // const fragmentShader = `
  160. // varying vec2 vUv;
  161. // uniform float ratios[${sizeList.length + 1}]; // 加入了起始点 0
  162. // uniform vec3 colors[${sizeList.length}];
  163. // vec3 lerp(vec3 a, vec3 b, float t) {
  164. // return a + t * (b - a);
  165. // }
  166. // void main() {
  167. // int index = 0;
  168. // for(int i = 1; i <= ${sizeList.length}; i++) {
  169. // if(vUv.x >= ratios[i-1] && vUv.x < ratios[i]) {
  170. // index = i - 1;
  171. // break;
  172. // }
  173. // }
  174. // // 如果是最后一个颜色块,直接使用其颜色
  175. // if (index == ${sizeList.length - 1}) {
  176. // gl_FragColor = vec4(colors[index], 1.0);
  177. // } else {
  178. // // 计算该像素在当前颜色块内的相对位置
  179. // float t = (vUv.x - ratios[index]) / (ratios[index + 1] - ratios[index]);
  180. // // 在相邻颜色间进行线性插值
  181. // vec3 color = lerp(colors[index], colors[index + 1], t);
  182. // gl_FragColor = vec4(color, 1.0);
  183. // }
  184. // }
  185. // `;
  186. // 创建着色器材质
  187. const material = new THREE.ShaderMaterial({
  188. uniforms: {
  189. ratios: { value: accumulatedRatios },
  190. colors: { value: colorsArray },
  191. },
  192. vertexShader: vertexShader,
  193. fragmentShader: fragmentShader,
  194. depthTest: false,
  195. depthWrite: false,
  196. });
  197. // // 当 sizeList 数据变化时调用此函数
  198. // function updateSizeList(newSizeList) {
  199. // accumulatedRatios.length = 0; // 清空累积比例数组
  200. // updateShaderData(newSizeList);
  201. // material.uniforms.ratios.value = accumulatedRatios;
  202. // material.uniforms.colors.value = colorsArray;
  203. // material.needsUpdate = true;
  204. // }
  205. // 创建网格并添加到场景中
  206. const plane = new THREE.Mesh(geometry, material);
  207. plane.rotation.x = -Math.PI / 2;
  208. plane.position.set(-0.2, 0.15, -0.03);
  209. this.planeGroup.add(plane);
  210. this.group.add(this.planeGroup);
  211. };
  212. // 清除抽采单元绘制面
  213. clearPlanes = () => {
  214. for (let i = 0; i < this.planeNum; i++) {
  215. const plane = this.planeGroup.getObjectByName(`unit${i}`);
  216. const label = this.planeGroup.getObjectByName(`planeText${i}`);
  217. if (plane) this.planeGroup.remove(plane);
  218. if (label) this.planeGroup.remove(label);
  219. }
  220. };
  221. // 抽采单元内容显示
  222. setCss3D = () => {
  223. const sizeList = [0.2, 0.3, 0.1, 0.2, 0.2];
  224. // const sizeList = [0.4, 0.5, 0.1];
  225. // width = 7.713 height =3.717
  226. let leftW = 0;
  227. for (let i = 0; i < sizeList.length; i++) {
  228. const label = setTag3D(`抽采单元${i + 1}`, 'gas_unit_text');
  229. label.scale.set(0.02, 0.02, 0.02); //根据相机渲染范围控制HTML 3D标签尺寸
  230. label.position.set((leftW + sizeList[i] / 2) * 7.913 - 4.22, 0.015, 0.142);
  231. label.name = 'planeText' + i;
  232. this.planeGroup.add(label);
  233. const obj = this.group.getObjectByName(`unitText${i}`);
  234. if (!obj) {
  235. const element = document.getElementById(`gasUnitBox${i + 1}`) as HTMLElement;
  236. if (element) {
  237. const gasUnitCSS3D = new CSS3DObject(element);
  238. gasUnitCSS3D.name = `unitText${i}`;
  239. gasUnitCSS3D.scale.set(0.01, 0.01, 0.01);
  240. gasUnitCSS3D.position.set((leftW + sizeList[i] / 2) * 10.93 - 11.17, 0.015, -3.442);
  241. gasUnitCSS3D.lookAt(gasUnitCSS3D.position.x, gasUnitCSS3D.position.y + 1.2, gasUnitCSS3D.position.y);
  242. this.planeGroup.add(gasUnitCSS3D);
  243. }
  244. }
  245. leftW += sizeList[i];
  246. }
  247. };
  248. // 显示或隐藏抽采单元显示内容
  249. changeCss3D = (isHide) => {
  250. for (let i = 0; i < this.planeNum; i++) {
  251. const obj = this.group.getObjectByName(`unitText${i}`);
  252. if (obj) {
  253. obj.visible = isHide;
  254. }
  255. }
  256. };
  257. // 清除抽采单元显示内容
  258. clearCss3D = () => {
  259. const obj = this.group.getObjectByName(`unitText`);
  260. if (obj) this.group.remove(obj);
  261. const element = document.getElementById(`gasUnitBox`) as HTMLElement;
  262. if (element) {
  263. element.remove();
  264. }
  265. for (let i = 0; i < this.planeNum; i++) {
  266. const label = this.planeGroup.getObjectByName(`planeText${i}`);
  267. if (label) this.planeGroup.remove(label);
  268. }
  269. };
  270. /* 点击 */
  271. mousedownModel(rayCaster: THREE.Raycaster) {
  272. const intersects = rayCaster?.intersectObjects([...this.planeGroup.children]) as THREE.Intersection[];
  273. // 判断是否点击到视频
  274. intersects.find((intersect) => {
  275. const intersectedObject = intersects[0].object;
  276. // 如果对象是我们的平面,并且它有自定义属性来标识它的 UV 范围
  277. if (intersectedObject && intersectedObject.material && intersectedObject.material.uniforms) {
  278. const uv = intersects[0].uv; // 点击点的 UV 坐标
  279. const clickedRatio = uv.x; // 使用 UV 的 x 分量作为比例值
  280. // // 根据点击的比例找到对应的色块
  281. // let clickedColorIndex = -1;
  282. // for (let i = 0; i < accumulatedRatios.length - 1; i++) {
  283. // if (clickedRatio >= accumulatedRatios[i] && clickedRatio < accumulatedRatios[i + 1]) {
  284. // clickedColorIndex = i;
  285. // break;
  286. // }
  287. // }
  288. // if (clickedColorIndex !== -1) {
  289. // // 提供反馈,例如打印信息到控制台
  290. // console.log(`Clicked color block ${clickedColorIndex}:`, sizeList[clickedColorIndex]);
  291. // // 这里你可以做更多事情,如改变颜色、显示弹窗等
  292. // }
  293. }
  294. return false;
  295. });
  296. this.render();
  297. }
  298. mouseUpModel() {
  299. //
  300. }
  301. mountedThree() {
  302. return new Promise(async (resolve) => {
  303. this.model.renderer.sortObjects = true;
  304. this.model.orbitControls.update();
  305. this.model.setGLTFModel(['workFace1'], this.group).then(async () => {
  306. this.group.children.forEach((object: THREE.Object3D) => {
  307. if (object.name.startsWith('workFace')) {
  308. setModalCenter(object);
  309. }
  310. });
  311. this.group.name = this.modelName;
  312. this.addLight();
  313. resolve(null);
  314. });
  315. });
  316. }
  317. destroy() {
  318. this.model.clearGroup(this.group);
  319. this.model = null;
  320. this.group = null;
  321. this.bloomComposer?.dispose();
  322. this.finalComposer?.dispose();
  323. }
  324. }
  325. export default GasAssessmen;