123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- import * as THREE from 'three';
- import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
- import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
- import { setModalCenter, setTag3D, gradientColors } from '/@/utils/threejs/util';
- import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
- import { green, yellow } from '@ant-design/colors';
- // import * as dat from 'dat.gui';
- // const gui = new dat.GUI();
- // gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
- type Unit = {
- id: string;
- ratio: number;
- color: THREE.Color;
- };
- class GasAssessmen {
- model;
- modelName = 'workFace';
- group: THREE.Object3D = new THREE.Object3D();
- planeGroup: THREE.Group = new THREE.Group();
- bloomComposer: EffectComposer | null = null;
- finalComposer: EffectComposer | null = null;
- outlinePass: OutlinePass | null = null;
- positions: THREE.Vector3[][] = [];
- bloomLayer = new THREE.Layers();
- darkMaterial = new THREE.MeshBasicMaterial({ color: 'black', transparent: true, side: THREE.DoubleSide });
- materials = {};
- glob = {
- ENTIRE_SCENE: 0,
- BLOOM_SCENE: 10,
- N: 100,
- };
- locationTexture: THREE.Texture | null = null;
- warningLocationTexture: THREE.Texture | null = null;
- errorLocationTexture: THREE.Texture | null = null;
- playerStartClickTime1 = new Date().getTime();
- playerStartClickTime2 = new Date().getTime();
- planeNum = 0;
- unitList: Unit[] = [];
- constructor(model) {
- this.model = model;
- this.group.name = this.modelName;
- }
- addLight() {
- // const _this = this;
- const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
- directionalLight.position.set(-196, 150, 258);
- this.group.add(directionalLight);
- directionalLight.target = this.group;
- }
- render() {
- this.model.renderer?.render(this.model.scene as THREE.Scene, this.model.camera as THREE.PerspectiveCamera);
- }
- setPlanes1 = (n) => {
- // const sizeList = [0.2, 0.3, 0.1, 0.2, 0.2];
- const colors = {
- c1: new THREE.Color(0x00fe00), // >90
- c2: new THREE.Color(0xf9b866), // >75 <90 249,184,102
- c3: new THREE.Color(0xfefe00), // 50-75 254,254,0
- c4: new THREE.Color(0xfe6600), // 25-50 254,102,0
- c5: new THREE.Color(0xb00101), // <25 176,1,1
- };
- const sizeList = [
- {
- id: '111',
- ratio: 0.5,
- color: colors.c1,
- },
- {
- id: '222',
- ratio: 0.3,
- color: colors.c2,
- },
- {
- id: '333',
- ratio: 0.2,
- color: colors.c4,
- },
- // {
- // id: '444',
- // ratio: 0.2,
- // color: colors.c5,
- // },
- // {
- // id: '555',
- // ratio: 0.2,
- // color: colors.c3,
- // },
- ];
- this.unitList = sizeList;
- // width = 7.713 height =3.717
- // const sizeList = [
- // {
- // ratio: 0.4,
- // color: colors.c1,
- // },
- // {
- // ratio: 0.5,
- // color: colors.c2,
- // },
- // {
- // ratio: 0.1,
- // color: colors.c4,
- // },
- // ];
- const geometry = new THREE.PlaneGeometry(7.723, 3.72, 1, 1);
- // 初始化累积比例数组和颜色数组
- const accumulatedRatios = [];
- const colorsArray = new Float32Array(3 * sizeList.length);
- // 计算累积比例和颜色数组
- function updateShaderData(sizeList) {
- let accRatio = 0;
- for (let i = 0; i < sizeList.length; i++) {
- const item = sizeList[i];
- accRatio += item.ratio;
- accumulatedRatios.push(accRatio);
- colorsArray[i * 3] = item.color.r;
- colorsArray[i * 3 + 1] = item.color.g;
- colorsArray[i * 3 + 2] = item.color.b;
- }
- }
- updateShaderData(sizeList); // 初始调用
- // 定义着色器代码
- const vertexShader = `
- varying vec2 vUv;
- void main() {
- vUv = uv;
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
- }
- `;
- const fragmentShader = `
- varying vec2 vUv;
- uniform float ratios[${sizeList.length}];
- uniform vec3 colors[${sizeList.length}];
-
- void main() {
- for(int i = 0; i < ${sizeList.length}; i++) {
- if(vUv.x < ratios[i]) {
- gl_FragColor = vec4(colors[i], 1.0);
- return;
- }
- }
- gl_FragColor = vec4(0.0, 0.0, 0.0, 0.5);
- }
- `;
- // const fragmentShader = `
- // varying vec2 vUv;
- // uniform float ratios[${sizeList.length + 1}]; // 加入了起始点 0
- // uniform vec3 colors[${sizeList.length}];
- // vec3 lerp(vec3 a, vec3 b, float t) {
- // return a + t * (b - a);
- // }
- // void main() {
- // int index = 0;
- // for(int i = 1; i <= ${sizeList.length}; i++) {
- // if(vUv.x >= ratios[i-1] && vUv.x < ratios[i]) {
- // index = i - 1;
- // break;
- // }
- // }
- // // 如果是最后一个颜色块,直接使用其颜色
- // if (index == ${sizeList.length - 1}) {
- // gl_FragColor = vec4(colors[index], 1.0);
- // } else {
- // // 计算该像素在当前颜色块内的相对位置
- // float t = (vUv.x - ratios[index]) / (ratios[index + 1] - ratios[index]);
- // // 在相邻颜色间进行线性插值
- // vec3 color = lerp(colors[index], colors[index + 1], t);
- // gl_FragColor = vec4(color, 1.0);
- // }
- // }
- // `;
- // 创建着色器材质
- const material = new THREE.ShaderMaterial({
- uniforms: {
- ratios: { value: accumulatedRatios },
- colors: { value: colorsArray },
- },
- vertexShader: vertexShader,
- fragmentShader: fragmentShader,
- depthTest: false,
- depthWrite: false,
- });
- // // 当 sizeList 数据变化时调用此函数
- // function updateSizeList(newSizeList) {
- // accumulatedRatios.length = 0; // 清空累积比例数组
- // updateShaderData(newSizeList);
- // material.uniforms.ratios.value = accumulatedRatios;
- // material.uniforms.colors.value = colorsArray;
- // material.needsUpdate = true;
- // }
- // 创建网格并添加到场景中
- const plane = new THREE.Mesh(geometry, material);
- plane.rotation.x = -Math.PI / 2;
- plane.position.set(-0.2, 0.15, -0.03);
- plane.name = 'unit';
- this.planeGroup.add(plane);
- this.group.add(this.planeGroup);
- };
- setPlanes = (unitList: any[]) => {
- if (!unitList || unitList.length === 0) return;
- // 要根据unitList来计算比例
- type Point = { u: number; v: number };
- let max = 0;
- const unitLen = unitList[unitList.length - 1]['unitLen'].split(',');
- max = Math.max(max, ...unitLen);
- const regions = <{ points: Point[]; color: any[] }[]>[];
- for (let i = 0; i < unitList.length; i++) {
- const item = unitList[i];
- const unitRatio: Point[] = [];
- const color = new THREE.Color(item['unitColor']);
- if (item['unitLen']) {
- const unitLen = item['unitLen'].split(',');
- const sortList = [1, 0, 2, 3];
- for (let j = 0; j < sortList.length; j++) {
- const x = unitLen[sortList[j]];
- const unitU = Number((x / max).toFixed(2));
- const unitV = sortList[j] % 2 === 0 ? 0.0 : 1.0;
- unitRatio.push({
- u: unitU == 0 ? 0.0 : unitU,
- v: unitV == 0 ? 0.0 : unitV,
- });
- }
- }
- regions.push({
- points: unitRatio,
- color: [color.r, color.g, color.b],
- // color: [1, 0, 0],
- });
- }
- // 自定义着色器
- const vertexShader = `
- varying vec2 vUv;
- void main() {
- vUv = uv; // 传递纹理坐标
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
- }
- `;
- const fragmentShader = `
- uniform vec3 regionColors[${regions.length}]; // 区域颜色数组
- uniform vec4 regionPoints[${regions.length * 4}]; // 区域顶点数组 (每个区域4个点)
- varying vec2 vUv;
- // 判断点是否在四边形内(通过叉积法)
- bool isPointInQuad(vec2 p, vec2 a, vec2 b, vec2 c, vec2 d) {
- vec2 ab = b - a;
- vec2 bc = c - b;
- vec2 cd = d - c;
- vec2 da = a - d;
- vec2 ap = p - a;
- vec2 bp = p - b;
- vec2 cp = p - c;
- vec2 dp = p - d;
- float cross1 = ab.x * ap.y - ab.y * ap.x;
- float cross2 = bc.x * bp.y - bc.y * bp.x;
- float cross3 = cd.x * cp.y - cd.y * cp.x;
- float cross4 = da.x * dp.y - da.y * dp.x;
- return (cross1 >= 0.0 && cross2 >= 0.0 && cross3 >= 0.0 && cross4 >= 0.0) ||
- (cross1 <= 0.0 && cross2 <= 0.0 && cross3 <= 0.0 && cross4 <= 0.0);
- }
- void main() {
- vec3 finalColor = vec3(0.0);
- for (int i = 0; i < ${regions.length}; i++) {
- vec4 p1 = regionPoints[i * 4];
- vec4 p2 = regionPoints[i * 4 + 1];
- vec4 p3 = regionPoints[i * 4 + 2];
- vec4 p4 = regionPoints[i * 4 + 3];
- // 判断当前片段是否在梯形区域内
- if (isPointInQuad(vUv, p1.xy, p2.xy, p3.xy, p4.xy)) {
- finalColor = regionColors[i];
- break;
- }
- }
- gl_FragColor = vec4(finalColor, 1.0);
- }
- `;
- const geometry = new THREE.PlaneGeometry(7.723, 3.72, 1, 1);
- // 创建材质
- const material = new THREE.ShaderMaterial({
- vertexShader,
- fragmentShader,
- uniforms: {
- regionColors: { value: regions.map((region) => new THREE.Vector3(...region.color)) },
- regionPoints: {
- value: regions.flatMap((region) => region.points.map((p) => new THREE.Vector4(p.u, p.v, 0, 0))),
- },
- },
- depthTest: false,
- depthWrite: false,
- });
- // 创建网格并添加到场景中
- const plane = new THREE.Mesh(geometry, material);
- plane.rotation.x = -Math.PI / 2;
- plane.position.set(-0.2, 0.15, -0.03);
- plane.name = 'unit';
- this.planeGroup.add(plane);
- this.group.add(this.planeGroup);
- };
- // 清除抽采单元绘制面
- clearPlanes = () => {
- for (let i = 0; i < this.planeNum; i++) {
- const plane = this.planeGroup.getObjectByName(`unit${i}`);
- const label = this.planeGroup.getObjectByName(`planeText${i}`);
- if (plane) this.planeGroup.remove(plane);
- if (label) this.planeGroup.remove(label);
- }
- };
- // 抽采单元内容显示
- setCss3D = (unitList: any[]) => {
- const sizeList: number[] = [];
- if (!unitList || unitList.length === 0) return;
- let max = 0;
- const unitLen = unitList[unitList.length - 1]['unitLen'].split(',');
- max = Math.max(max, ...unitLen);
- for (let i = 0; i < unitList.length; i++) {
- const item = unitList[i];
- const unitLen = item['unitLen'].split(',');
- const unitMax = Math.max(...unitLen);
- const unitMin = Math.min(...unitLen);
- sizeList.push((unitMax - unitMin) / max);
- }
- let leftW = 0;
- for (let i = 0; i < sizeList.length; i++) {
- const label = setTag3D(`抽采单元${i + 1}`, 'gas_unit_text');
- label.scale.set(0.02, 0.02, 0.02); //根据相机渲染范围控制HTML 3D标签尺寸
- label.position.set((leftW + sizeList[i] / 2) * 7.913 - 4.22, 0.015, 0.142);
- label.name = 'planeText' + i;
- this.planeGroup.add(label);
- const obj = this.group.getObjectByName(`unitText${i}`);
- if (!obj) {
- const element = document.getElementById(`gasUnitBox${i + 1}`) as HTMLElement;
- if (element) {
- const gasUnitCSS3D = new CSS3DObject(element);
- gasUnitCSS3D.name = `unitText${i}`;
- gasUnitCSS3D.scale.set(0.01, 0.01, 0.01);
- gasUnitCSS3D.position.set((leftW + sizeList[i] / 2) * 10.93 - 11.17, 0.015, -3.442);
- gasUnitCSS3D.lookAt(gasUnitCSS3D.position.x, gasUnitCSS3D.position.y + 1.2, gasUnitCSS3D.position.y);
- this.planeGroup.add(gasUnitCSS3D);
- }
- }
- leftW += sizeList[i];
- }
- };
- // 显示或隐藏抽采单元显示内容
- changeCss3D = () => {
- for (let i = 0; i < this.planeNum; i++) {
- const planeText = this.planeGroup.getObjectByName(`planeText${i}`);
- this.planeGroup.remove(planeText);
- const unitText = this.planeGroup.getObjectByName(`unitText${i}`);
- this.planeGroup.remove(unitText);
- }
- };
- // 清除抽采单元显示内容
- clearCss3D = () => {
- // const obj = this.group.getObjectByName(`unitText`);
- // if (obj) this.group.remove(obj);
- // const element = document.getElementById(`gasUnitBox`) as HTMLElement;
- // if (element) {
- // element.remove();
- // }
- // for (let i = 0; i < this.planeNum; i++) {
- // const label = this.planeGroup.getObjectByName(`planeText${i}`);
- // if (label) this.planeGroup.remove(label);
- // }
- this.model.clearGroup(this.planeGroup);
- this.planeGroup = new THREE.Group();
- };
- /* 点击 */
- mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
- // 判断是否点击到视频
- return new Promise((resolve) => {
- intersects.find((intersect) => {
- const intersectedObject = intersect.object;
- if (intersectedObject.name == 'unit') {
- // 如果对象是我们的平面,并且它有自定义属性来标识它的 UV 范围
- if (intersectedObject && intersectedObject.material && intersectedObject.material.uniforms) {
- const uv = intersect.uv; // 点击点的 UV 坐标
- const clickedRatio = uv.x; // 使用 UV 的 x 分量作为比例值
- // 根据点击的比例找到对应的色块
- let clickedColorIndex = -1;
- let accRatio = 0;
- for (let i = 0; i < this.unitList.length - 1; i++) {
- const unitData = this.unitList[i];
- accRatio += unitData.ratio;
- if (clickedRatio < accRatio) {
- clickedColorIndex = i;
- break;
- }
- }
- if (clickedColorIndex !== -1) {
- const unitData = this.unitList[clickedColorIndex];
- const unitId = unitData.id;
- // window.open(`${location.origin}/gasUnitAssessment/home?id=${unitId}`);
- resolve(unitId);
- }
- }
- return true;
- }
- return false;
- });
- this.render();
- });
- }
- mouseUpModel() {
- //
- }
- mountedThree() {
- return new Promise(async (resolve) => {
- this.model.renderer.sortObjects = true;
- this.model.orbitControls.update();
- this.model.setGLTFModel(['workFace1'], this.group).then(async () => {
- this.group.children.forEach((object: THREE.Object3D) => {
- if (object.name.startsWith('workFace')) {
- setModalCenter(object);
- }
- });
- this.group.name = this.modelName;
- this.addLight();
- resolve(null);
- });
- });
- }
- destroy() {
- this.model.clearGroup(this.group);
- this.model = null;
- this.group = null;
- this.bloomComposer?.dispose();
- this.finalComposer?.dispose();
- }
- }
- export default GasAssessmen;
|