import * as THREE from 'three'; import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js'; import { animateCamera, getTextCanvas, renderVideo } from '/@/utils/threejs/util'; import UseThree from '../../../../utils/threejs/useThree'; import fcFan from './fcfanLocal.three'; import fmFan from './fmfanLocal.three'; import Smoke from '/@/views/vent/comment/threejs/Smoke'; import gsap from 'gsap'; const modelName = 'jbfj_hd'; // 模型对象、 文字对象 let model, group, fcFanObj, fmFanObj, player1, fanType, topSmoke, downSmoke, playerStartClickTime1 = new Date().getTime(), playerStartClickTime2 = new Date().getTime(); // 打灯光 const addLight = (scene) => { // const pointLight2 = new THREE.PointLight(0xffeeee, 1.5, 100); // pointLight2.position.set(-120, 16, -33); // pointLight2.shadow.bias = 0.05; // scene.add(pointLight2); // // const pointLight3 = new THREE.PointLight(0xffffff, 1, 40); // pointLight3.position.set(-66, 40, 1); // pointLight3.shadow.bias = 0.05; // scene.add(pointLight3); // // const pointLight4 = new THREE.PointLight(0xffeeee, 0.6, 230); // pointLight4.position.set(-18, 30, 12); // pointLight4.shadow.bias = 0.05; // scene.add(pointLight4); // // const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 90); // pointLight5.position.set(-57, 7, -30); // pointLight5.shadow.bias = 0.05; // scene.add(pointLight5); // // const pointLight6 = new THREE.PointLight(0xffffff, 0.8, 270); // pointLight6.position.set(72, -33, 11.4); // pointLight6.shadow.bias = 0.05; // scene.add(pointLight6); const pointLight7 = new THREE.PointLight(0xffffff, 0.8, 500); pointLight7.position.set(-20, -43, 12); pointLight7.shadow.bias = -0.05; scene.add(pointLight7); const spotLight = new THREE.SpotLight(); spotLight.angle = Math.PI / 16; spotLight.penumbra = 0; spotLight.castShadow = true; spotLight.position.set(0, 463, 687); scene.add(spotLight); spotLight.shadow.camera.near = 0.5; // default spotLight.shadow.camera.far = 1000; // default spotLight.shadow.focus = 1; spotLight.shadow.bias = -0.000002; // gui.add(pointLight6.position, 'x', -200, 200); // gui.add(pointLight6.position, 'y', -200, 200); // gui.add(pointLight6.position, 'z', -200, 200); // gui.add(pointLight6, 'distance', 0, 500); }; // 重置摄像头 const resetCamera = () => { // model.camera.position.set(0, 30, 80); model.camera.position.set(0, -1000, 100); // model.camera.fov = 20 model.camera.far = 1000; model.orbitControls?.update(); model.camera.updateProjectionMatrix(); }; // 设置模型位置 const setModalPosition = () => { group.position.set(0, 18, -50); group.rotation.y = Math.PI / 2; }; const setControls = () => { model.orbitControls.panSpeed = 0.5; model.orbitControls.rotateSpeed = 0.5; model.orbitControls.maxPolarAngle = Math.PI / 2.4; model.orbitControls.minPolarAngle = Math.PI / 3; }; // 切换局部通风机类型 export const setModelType = (type) => { fanType = type; return new Promise((resolve) => { // 显示双道风窗 if (fanType === 'fm') { if (group.getObjectByName('jbfj_fc')) { group.remove(fcFanObj.group); } setTimeout(async () => { const position = fmFanObj.group; const oldCameraPosition = { x: 500, y: 100, z: 500 }; await animateCamera( oldCameraPosition, oldCameraPosition, { x: 0, y: 30, z: 80 }, { x: position.x, y: position.y, z: position.z }, model, 0.8 ); group.add(fmFanObj.group); resolve(null); }, 300); } else if (fanType === 'fc') { // 显示单道风窗 if (group.getObjectByName('jbfj_fm')) { group.remove(fmFanObj.group); } setTimeout(async () => { const position = fcFanObj.group; const oldCameraPosition = { x: 500, y: 100, z: 500 }; await animateCamera( oldCameraPosition, oldCameraPosition, { x: 0, y: 30, z: 80 }, { x: position.x, y: position.y, z: position.z }, model, 0.8 ); group.add(fcFanObj.group); resolve(null); }, 300); } }); }; /* 添加监控数据 */ export const addText = (selectData) => { if (!group) { return; } const textArr = [ { text: `煤矿巷道远程局部风机系统`, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 50, y: 110, }, { text: `进风量(m³/min):`, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 0, y: 165, }, { text: `${selectData.frontRearDP}`, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 290, y: 165, }, { text: `供风量(m³/min): `, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 0, y: 220, }, { text: ` ${selectData.sourcePressure}`, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 280, y: 220, }, { text: `故障诊断:`, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 0, y: 275, }, { text: `${selectData.fault}`, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 280, y: 275, }, { text: `煤炭科学技术研究院有限公司研制`, font: 'normal 28px Arial', color: '#009900', strokeStyle: '#002200', x: 20, y: 325, }, ]; getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => { const textMap = new THREE.CanvasTexture(canvas); // 关键一步 const textMaterial = new THREE.MeshBasicMaterial({ // 关于材质并未讲解 实操即可熟悉 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质. map: textMap, // 设置纹理贴图 transparent: true, side: THREE.FrontSide, // 这里是双面渲染的意思 }); textMaterial.blending = THREE.CustomBlending; const monitorPlane = group.getObjectByName('monitorText'); if (monitorPlane) { monitorPlane.material = textMaterial; } else { const planeGeometry = new THREE.PlaneGeometry(526, 346); // 平面3维几何体PlaneGeometry const planeMesh = new THREE.Mesh(planeGeometry, textMaterial); planeMesh.name = 'monitorText'; planeMesh.scale.set(0.0135, 0.0135, 0.0135); planeMesh.rotation.y = -Math.PI / 2; planeMesh.position.set(-84.79, 0.82, 17.01); group.add(planeMesh); } }); }; // // css3D文字 export const addCssText = () => { if (!group.getObjectByName('text1')) { const element = document.getElementById('inputBox') as HTMLElement; const fanLocalCSS3D = new CSS3DObject(element); fanLocalCSS3D.name = 'text1'; fanLocalCSS3D.scale.set(0.04, 0.04, 0.04); fanLocalCSS3D.rotation.y = -Math.PI / 2; fanLocalCSS3D.position.set(-82.96, 5.97, -0.84); group.add(fanLocalCSS3D); } if (!group.getObjectByName('text2')) { const element = document.getElementById('outBox') as HTMLElement; const fanLocalCSS3D = new CSS3DObject(element); fanLocalCSS3D.name = 'text2'; fanLocalCSS3D.scale.set(0.1, 0.1, 0.1); fanLocalCSS3D.rotation.y = -Math.PI / 2; fanLocalCSS3D.position.set(74.63, 13.54, 3.84); group.add(fanLocalCSS3D); } if (!group.getObjectByName('text3')) { const element = document.getElementById('returnBox') as HTMLElement; const fanLocalCSS3D = new CSS3DObject(element); fanLocalCSS3D.name = 'text3'; fanLocalCSS3D.scale.set(0.1, 0.1, 0.1); fanLocalCSS3D.rotation.y = -Math.PI / 2; fanLocalCSS3D.position.set(74.63, 10.05, -37.23); group.add(fanLocalCSS3D); } if (!group.getObjectByName('text4')) { const element = document.getElementById('gateBox') as HTMLElement; const fanLocalCSS3D = new CSS3DObject(element); fanLocalCSS3D.name = 'text4'; fanLocalCSS3D.scale.set(0.04, 0.04, 0.04); fanLocalCSS3D.rotation.y = -Math.PI / 2; fanLocalCSS3D.position.set(-73.13, 8.44, -23.52); group.add(fanLocalCSS3D); } if (!group.getObjectByName('text5')) { const element = document.getElementById('windownBox') as HTMLElement; const fanLocalCSS3D = new CSS3DObject(element); fanLocalCSS3D.name = 'text5'; fanLocalCSS3D.scale.set(0.07, 0.07, 0.07); fanLocalCSS3D.rotation.y = -Math.PI / 2; fanLocalCSS3D.position.set(-28.44, 9.78, -40.42); group.add(fanLocalCSS3D); } }; export const playSmoke = (controlType, deviceType, frequency, state) => { if (frequency) { setSmokeFrequency(deviceType, frequency); } if (controlType === 'startSmoke') { runFly(deviceType, state); } }; const initFly = () => { const topCurve = [ { path0: new THREE.Vector3(-94.84, 2.359, 3.61), path1: new THREE.Vector3(-85.678, 2.359, 3.61), isSpread: true, spreadDirection: -1, // }, { path0: new THREE.Vector3(-85.678, 2.352, 3.66), path1: new THREE.Vector3(-85.636, 2.353, -3.829), isSpread: false, spreadDirection: 0, }, { path0: new THREE.Vector3(-85.636, 2.353, -3.829), path1: new THREE.Vector3(-85.636, 1.026, -5.881), isSpread: false, spreadDirection: 0, }, { path0: new THREE.Vector3(-85.636, 1.026, -5.881), path1: new THREE.Vector3(-85.618, 0.887, -12.862), isSpread: false, spreadDirection: 0, }, { path0: new THREE.Vector3(-85.618, 0.827, -12.962), path1: new THREE.Vector3(80.404, 0.827, -12.962), isSpread: false, spreadDirection: 0, }, { path0: new THREE.Vector3(80.404, 0.827, -12.962), path1: new THREE.Vector3(93.164, 0.85, -12.962), isSpread: true, spreadDirection: 1, // 1是由小变大,-1是由大变小 }, ]; const downCurve = [ { path0: new THREE.Vector3(-94.84, -0.388, 3.61), path1: new THREE.Vector3(-85.678, -0.393, 3.61), isSpread: true, spreadDirection: -1, // }, { path0: new THREE.Vector3(-85.678, -0.393, 3.275), path1: new THREE.Vector3(-85.636, -0.392, -3.829), isSpread: false, spreadDirection: 0, }, { path0: new THREE.Vector3(-85.636, -0.392, -3.829), path1: new THREE.Vector3(-85.636, 0.926, -5.881), isSpread: false, spreadDirection: 0, }, { path0: new THREE.Vector3(-85.636, 1.026, -5.881), path1: new THREE.Vector3(-85.618, 0.887, -12.862), isSpread: false, spreadDirection: 0, }, { path0: new THREE.Vector3(-85.618, 0.887, -12.962), path1: new THREE.Vector3(80.404, 0.887, -12.962), isSpread: false, spreadDirection: 0, }, { path0: new THREE.Vector3(80.404, 0.887, -12.962), path1: new THREE.Vector3(93.164, 0.91, -12.962), isSpread: true, spreadDirection: 1, // 1是由小变大,-1是由大变小 }, ]; // const windowCurve = new THREE.CatmullRomCurve3([ // new THREE.Vector3(83.479, 0.052, -16.487), // new THREE.Vector3(-90.436, 0.0, -16.32), // new THREE.Vector3(-90.712, 0.0, -16.395), // new THREE.Vector3(-90.77, 0.0, -16.663), // new THREE.Vector3(-90.77, 0.0, -36.015), // ]); // const gateCurve = new THREE.CatmullRomCurve3([ // new THREE.Vector3(83.479, 0.052, -16.487), // new THREE.Vector3(-30.267, 0.013, -16.458), // new THREE.Vector3(-30.62, 0.013, -16.523), // new THREE.Vector3(-30.727, 0.013, -16.831), // new THREE.Vector3(-30.727, 0.013, -35.84), // ]); if (!topSmoke) { topSmoke = new Smoke('/model/img/texture-smoke.png', '#ffffff', 0, 0.8, 0.5, 400); topSmoke.setPath(topCurve); topSmoke.setPoints(); group?.add(topSmoke.points); } if (!downSmoke) { downSmoke = new Smoke('/model/img/texture-smoke.png', '#ffffff', 0, 0.8, 0.5, 400); downSmoke.setPath(downCurve); downSmoke.setPoints(); group?.add(downSmoke.points); } // if (!group.getObjectByName('gateFly')) { // } }; const runFly = (deviceType, state) => { if (state === 'open') { if (deviceType === 'top') { if (downSmoke.frameId) { downSmoke.stopSmoke(); } topSmoke.startSmoke(); } else { if (topSmoke.frameId) { topSmoke.stopSmoke(); } downSmoke.startSmoke(); } } else { if (downSmoke.frameId) { downSmoke.stopSmoke(); } if (topSmoke.frameId) { topSmoke.stopSmoke(); } } }; // 调频 30-50 (life 300 - 800) , 25 = (800 - 300)/ 20 const setSmokeFrequency = (deviceType, frequency) => { const life = (frequency - 30) * 25; const duration = (Math.abs(life - topSmoke.life) / 500) * 25; let smoke; if (deviceType === 'top') { smoke = topSmoke; } else { smoke = downSmoke; } gsap.to(smoke, { life: life, duration: duration, ease: 'easeInCubic', overwrite: true, }); }; const clearFly = () => { if (topSmoke) topSmoke.clearSmoke(); if (downSmoke) downSmoke.clearSmoke(); }; // 初始化事件 const startAnimation = () => { // 定义鼠标点击事件 model.canvasContainer?.addEventListener('mousedown', mouseEvent.bind(null)); model.canvasContainer?.addEventListener('pointerup', (event) => { event.stopPropagation(); }); }; // 鼠标点击、松开事件 const mouseEvent = (event) => { event.stopPropagation(); // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1) model.mouse.x = ((event.clientX - model.canvasContainer.getBoundingClientRect().left) / model.canvasContainer.clientWidth) * 2 - 1; model.mouse.y = -((event.clientY - model.canvasContainer.getBoundingClientRect().top) / model.canvasContainer.clientHeight) * 2 + 1; (model.rayCaster as THREE.Raycaster).setFromCamera(model.mouse, model.camera as THREE.Camera); if (group) { const intersects = model.rayCaster?.intersectObjects(group.children, false) as THREE.Intersection[]; if (intersects.length > 0) { intersects.find((intersect) => { const mesh = intersect.object; if (mesh.name === 'player1') { if (new Date().getTime() - playerStartClickTime1 < 400) { // 双击,视频放大 if (player1) { player1.requestFullscreen(); } } playerStartClickTime1 = new Date().getTime(); return true; } }); } } }; export const mountedThree = (playerVal1) => { player1 = playerVal1; return new Promise((resolve) => { model = new UseThree('#fanLocal3D', '#fanLocal3DCSS'); model.setEnvMap('test1'); model.renderer.toneMappingExposure = 0.8; addLight(model.scene); resetCamera(); model.setModel(modelName).then(async (gltf) => { group = gltf.scene; model.scene?.add(group); if (gltf.animations && gltf.animations.length > 0) { model.mixers = []; model.animations = []; gltf.animations.forEach((animation) => { const mixer = new THREE.AnimationMixer(group); model.mixers.push(mixer); model.animations.push(animation); }); } setModalPosition(); setControls(); initFly(); model.animate(); fcFanObj = new fcFan(model); await fcFanObj.mountedThree(); fmFanObj = new fmFan(model); await fmFanObj.mountedThree(); const videoPlayer1 = document.getElementById('jb-player1')?.getElementsByClassName('vjs-tech')[0]; if (videoPlayer1) { const mesh = renderVideo(group, videoPlayer1, 'player1'); mesh.scale.set(0.222, 0.19, 0.2); mesh.position.set(-84.87, 0.298, 24.76); mesh.rotation.y = -Math.PI / 2; group.add(mesh); } startAnimation(); resolve(model); }); }); }; export const destroy = () => { if (model) { clearFly(); if (fcFanObj) fcFanObj.destroy(); if (fmFanObj) fmFanObj.destroy(); model.deleteModal(); model = null; group = null; topSmoke = null; downSmoke = null; } };