import * as THREE from 'three'; import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js'; import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'; import { getTextCanvas, renderVideo, transScreenCoord } from '/@/utils/threejs/util'; import UseThree from '../../../../hooks/core/threejs/useThree'; import { animateCamera } from '/@/utils/threejs/util'; import { flyLine } from '/@/views/vent/comment/threejs/FlyLine'; import { createComposer } from '/@/views/vent/comment/threejs/bloomPass'; import * as dat from 'dat.gui'; import { drawHot } from '/@/utils/threejs/util'; import { useAppStore } from '/@/store/modules/app'; // const gui = new dat.GUI(); // gui.domElement.style = 'position:absolute;top:10px;right:10px;z-index:99999999999999'; const modelName = 'fm'; // 模型对象、 文字对象 let model, // group, fmCSS3D, //文字 isLRAnimation = true, // 是否开启左右摇摆动画 direction = 1, // 摇摆方向 animationtimer: NodeJS.Timeout | null, // 摇摆开启定时器 renderBloomPass, player1, player2, deviceDetailCSS3D, playerStartClickTime1 = new Date().getTime(), playerStartClickTime2 = new Date().getTime(); const appStore = useAppStore(); const clipActionArr = { frontDoor: null as unknown as THREE.AnimationAction, backDoor: null as unknown as THREE.AnimationAction, }; // 打灯光 const addLight = (scene) => { const pointLight2 = new THREE.PointLight(0xffeeee, 0.8, 300); pointLight2.position.set(-113, 29, 10); // light2.castShadow = true pointLight2.shadow.bias = -0.05; scene.add(pointLight2); // const pointLightHelper2 = new THREE.PointLightHelper( pointLight2, 1 ); // scene.add( pointLightHelper2 ); const pointLight3 = new THREE.PointLight(0xffffff, 0.8, 100); pointLight3.position.set(0, 30, 3); // light2.castShadow = true pointLight3.shadow.bias = -0.05; scene.add(pointLight3); // const pointLightHelper = new THREE.PointLightHelper( pointLight3, 1 ); // scene.add( pointLightHelper ); const pointLight4 = new THREE.PointLight(0xffeeee, 0.6, 100); pointLight4.position.set(-14, 29, 13); // light2.castShadow = true pointLight4.shadow.bias = -0.05; scene.add(pointLight4); // const pointLightHelper4 = new THREE.PointLightHelper( pointLight4, 1 ); // scene.add( pointLightHelper4 ); const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 100); pointLight5.position.set(80, 43, -5.3); // light2.castShadow = true pointLight5.shadow.bias = -0.05; scene.add(pointLight5); // const pointLightHelper5 = new THREE.PointLightHelper( pointLight5, 1 ); // scene.add( pointLightHelper5 ); const pointLight6 = new THREE.PointLight(0xffffff, 1, 300); // pointLight6.position.set(-47, 49, 12.9) pointLight6.position.set(-7, 40, 9); // light2.castShadow = true pointLight6.shadow.bias = -0.05; scene.add(pointLight6); // const pointLightHelper6 = new THREE.PointLightHelper( pointLight6, 1 ); // scene.add( pointLightHelper6 ); const pointLight7 = new THREE.PointLight(0xffffff, 0.8, 300); pointLight7.position.set(45, 51, -4.1); // light2.castShadow = true pointLight7.shadow.bias = -0.05; scene.add(pointLight7); // const pointLightHelper7 = new THREE.PointLightHelper( pointLight7, 1 ); // scene.add( pointLightHelper7 ); // const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); // directionalLight.target = group; // directionalLight.position.set(-18, 20, 39); // directionalLight.rotation.set(1.3, 0.3, 5.8); // scene.add(directionalLight); // const helper = new THREE.DirectionalLightHelper(directionalLight, 5); // scene.add(helper); const spotLight = new THREE.SpotLight(); spotLight.angle = Math.PI / 16; spotLight.penumbra = 0; // spotLight.castShadow = true; spotLight.position.set(-231, 463, 687); scene.add(spotLight); // spotLight.shadow.mapSize.width = 1500; // default // spotLight.shadow.mapSize.height = 800; // default spotLight.shadow.camera.near = 0.5; // default spotLight.shadow.camera.far = 1000; // default spotLight.shadow.focus = 1.2; spotLight.shadow.bias = -0.000002; // const spotLightHelper = new THREE.SpotLightHelper(spotLight); // scene.add(spotLightHelper); // model.canvasContainer?.appendChild(gui.domElement); // gui.add(directionalLight.position, 'x', -100, 100).step(1); // gui.add(directionalLight.position, 'y', -100, 100).step(1); // gui.add(directionalLight.position, 'z', -100, 100).step(1); // gui.add(directionalLight.rotation, 'x', -Math.PI, 2 * Math.PI).step(0.1); // gui.add(directionalLight.rotation, 'y', -Math.PI, 2 * Math.PI).step(0.1); // gui.add(directionalLight.rotation, 'z', -Math.PI, 2 * Math.PI).step(0.1); // gui.add(spotLight, 'angle', 0, Math.PI / 2); // gui.add(spotLight, 'distance', 0, 1000); // gui.add(pointLight6.position, 'x', -200, 200) // gui.add(pointLight6.position, 'y', -200, 200) // gui.add(pointLight6.position, 'z', -200, 200) }; // 重置摄像头 const resetCamera = () => { model.camera.far = 274; model.orbitControls?.update(); model.camera.updateProjectionMatrix(); }; // 设置模型位置 const setModalPosition = () => { group?.scale.set(22, 22, 22); group.position.set(-20, 20, 9); }; // // css3D文字 // const addFm1Text = () => { // fmCSS3D = new CSS3DObject(elementContent.value); // fmCSS3D.scale.set(0.13, 0.13, 0.13); // fmCSS3D.position.set(0, 52, 0); // fmCSS3D.lookAt(model.camera.position.clone()); // model?.scene.add(fmCSS3D); // }; /* 添加监控数据 */ export const addFmText = (selectData) => { if (!group) { return; } const textArr = [ { text: `煤矿巷道远程风门系统`, font: 'normal 2.2rem Arial', color: '#009900', strokeStyle: '#002200', x: 80, y: 95, }, { text: `压力(Pa):`, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 0, y: 155, }, { text: `${selectData.frontRearDP}`, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 290, y: 155, }, { text: `动力源压力(MPa): `, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 0, y: 215, }, { text: ` ${selectData.sourcePressure}`, font: 'normal 30px Arial', color: '#009900', strokeStyle: '#002200', x: 280, y: 215, }, { 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.002, 0.002, 0.002); planeMesh.position.set(-1.255, 0.09, -0.41); group.add(planeMesh); } }); }; /** 添加热点 */ const drawHots = () => { const hotPositions = [ { x: -0.37, y: 0.26, z: -0.32 }, { x: 0.28, y: -0.2, z: -0.43 }, { x: 0.55, y: -0.22, z: -0.38 }, ]; for (let i = 0; i < 3; i++) { const hotPoint = drawHot(0.1); const position = hotPositions[i]; // hotPoint.scale.set(0.3, 0.3, 0.3); hotPoint.position.set(position.x, position.y, position.z); hotPoint.name = 'hotPoint' + i; debugger; group.add(hotPoint); } }; /* 漫游路线 */ const createLine = () => { const position = model.camera.position.clone(); //创建样条曲线,作为运动轨迹 const curve = new THREE.CatmullRomCurve3([ new THREE.Vector3(position.x, position.y, position.z), new THREE.Vector3(26.586, 17.86, 14.144), new THREE.Vector3(-0.075, 19.669, 15.051), new THREE.Vector3(-154.882, 17.462, 14.981), // new THREE.Vector3(76, 28, 27), ]); const geometry = new THREE.BufferGeometry().setFromPoints(curve.getPoints(5000)); // 材质对象 const material = new THREE.LineBasicMaterial({ color: 'red', }); // 线条模型对象 const line = new THREE.Line(geometry, material); // model?.scene.add(line) // 线条对象添加到场景中 return curve; }; /* 开启漫游 */ const enterMY = () => { model.startAnimation = () => {}; model.camera.position.set(114.27, 15.293, 14.189); model.camera.rotation.set(-86.23, 69.89, 85.98); const curve = createLine(); let progress = 0; model.startMY = () => { if (progress <= 1 - 0.004 * 20) { const point = curve.getPointAt(progress); //获取样条曲线指定点坐标,作为相机的位置 const pointBox = curve.getPointAt(progress + 0.004 * 20); //获取样条曲线指定点坐标 model.camera.position.set(point.x, point.y, point.z); model.camera.lookAt(pointBox.x + 5, pointBox.y, pointBox.z); // model.orbitControls.position0.set(point.x, point.y, point.z) //非必要,场景有控件时才加上 // model.orbitControls.target.set(pointBox.x, pointBox.y , pointBox.z) //非必要,场景有控件时才加上 progress += 0.004; } else { // progress = 0 model.camera.position.set(30.328, 58.993, 148.315); model.camera.rotation.set(-27.88, 14.35, 7.47); model.camera.lookAt(0, 0, 0); model.startMY = () => {}; model.startAnimation = fmAnimation.bind(null); } }; }; /* 风门动画 */ const render = () => { if (!model) { return; } if (isLRAnimation && group) { // 左右摇摆动画 if (Math.abs(group.rotation.y) >= 0.2) { direction = -direction; group.rotation.y += 0.00002 * 30 * direction; } else { group.rotation.y += 0.00002 * 30 * direction; } } // // //自发光 // const screen = group.getObjectByName('对象156'); // if (screen) { // model.renderer.clearDepth(); // screen.layers.enable(31); // !!renderBloomPass && renderBloomPass(group); // } // 风门开关动画 const delta = model.clock?.getElapsedTime(); if (model.mixers[0]) model.mixers[0]?.update(delta); }; // 鼠标点击、松开事件 const mouseEvent = (event) => { const widthScale = appStore.getWidthScale; const heightScale = appStore.getHeightScale; // debugger; event.stopPropagation(); // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1) model.mouse.x = ((-model.canvasContainer.getBoundingClientRect().left * widthScale + event.clientX) / (model.canvasContainer.clientWidth * widthScale)) * 2 - 1; model.mouse.y = -((-model.canvasContainer.getBoundingClientRect().top + event.clientY) / (model.canvasContainer.clientHeight * heightScale)) * 2 + 1; (model.rayCaster as THREE.Raycaster).setFromCamera(model.mouse, model.camera as THREE.Camera); // 计算物体和射线的焦点 const intersects = model.rayCaster?.intersectObjects(group.children) as THREE.Intersection[]; if (intersects.length > 0) { isLRAnimation = false; if (animationtimer) { clearTimeout(animationtimer); animationtimer = null; } if (model.camera.layers.mask == -1) model.camera.layers.toggle(1); // 判断是否点击到视频 intersects.find((intersect) => { const mesh = intersect.object; // deviceDetailCSS3D?.layers.set(1); if (mesh.name === 'player1') { if (new Date().getTime() - playerStartClickTime1 < 400) { // model.orbitControls?.dispatchEvent.call(model.orbitControls, { type: 'end' }) // 双击,视频放大 if (player1) { player1.requestFullscreen(); } } playerStartClickTime1 = new Date().getTime(); return true; } else if (mesh.name === 'player2') { if (new Date().getTime() - playerStartClickTime2 < 400) { // model.orbitControls?.dispatchEvent.call(model.orbitControls, { type: 'end' }) // 双击,视频放大 if (player2) { player2.requestFullscreen(); } } playerStartClickTime2 = new Date().getTime(); return true; } else if (mesh.name.startsWith('hotPoint')) { if (deviceDetailCSS3D) { // document.getElementById('deviceCard') as HTMLElement; deviceDetailCSS3D.position.set(intersect.point.x, intersect.point.y + 18, intersect.point.z); console.log('[ deviceDetailCSS3D.position ] >', deviceDetailCSS3D.position); // deviceDetailCSS3D?.layers.set(0); model.camera.layers.enableAll(); return true; } // let position = new THREE.Vector3(mesh.position.x, mesh.position.y, 0.5); // position = position.applyMatrix4(group.matrixWorld); // model.scene.updateMatrixWorld(true); // // let worldPosition = new THREE.Vector3(); // // let worldScale = new THREE.Vector3(); // // worldPosition = mesh.getWorldPosition(worldPosition); // // worldScale = mesh.getWorldScale(worldScale); // const devicePosition = transScreenCoord(intersect.point, model.camera); // element.style.left = devicePosition.x + 'px'; // element.style.top = devicePosition.y + 'px'; // console.log('[ point 坐标位置 ] >', devicePosition, mesh.position); } else { // deviceDetailCSS3D?.layers.set(1); console.log('[ 点击事件 ] >'); } return false; }); } }; // 初始化左右摇摆动画 const startAnimation = () => { // 开启动画 model.startAnimation = render.bind(null); // 定义鼠标点击事件x model.canvasContainer?.addEventListener('mousedown', mouseEvent.bind(null)); model.canvasContainer?.addEventListener('pointerup', (event) => { event.stopPropagation(); // 10s后开始摆动 if (!animationtimer && !isLRAnimation) { animationtimer = setTimeout(() => { isLRAnimation = true; }, 10000); } }); }; /* 提取风门序列帧,初始化前后门动画 */ const initAnimation = () => { const tracks = model.animations[0].tracks; const fontTracks: any[] = [], backTracks: any[] = []; for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; if (track.name.startsWith('qianmen')) { fontTracks.push(track); } else if (track.name.startsWith('houmen')) { backTracks.push(track); } } const frontDoor = new THREE.AnimationClip('frontDoor', 4, fontTracks); const backDoor = new THREE.AnimationClip('backDoor', 4, backTracks); const arr = [frontDoor, backDoor]; arr.forEach((animationClip) => { const clipAction = model.mixers[0].clipAction(animationClip, group); clipAction.clampWhenFinished = true; clipAction.loop = THREE.LoopOnce; if (animationClip.name == 'frontDoor') clipActionArr.frontDoor = clipAction; if (animationClip.name == 'backDoor') clipActionArr.backDoor = clipAction; }); }; export const deviceDetailCard = (position = { x: 0, y: 0, z: 0 }) => { const element = document.getElementById('deviceCard') as HTMLElement; deviceDetailCSS3D = new CSS2DObject(element); deviceDetailCSS3D.name = 'deviceCard'; // deviceDetailCSS3D.scale.set(0.1, 0.1, 0.1); // deviceDetailCSS3D.scale.set(0.004, 0.004, 0.004); // deviceDetailCSS3D.rotation.y = -Math.PI / 2; deviceDetailCSS3D.position.set(position.x, position.y, position.z); deviceDetailCSS3D?.layers.set(1); // group.add(deviceDetailCSS3D); model.scene.add(deviceDetailCSS3D); }; // 播放动画 export const play = (handlerState) => { let handler = () => {}; switch (handlerState) { case 1: // 打开前门 handler = () => { clipActionArr.frontDoor.paused = true; clipActionArr.frontDoor.reset(); clipActionArr.frontDoor.time = 0.5; clipActionArr.frontDoor.timeScale = 0.01; clipActionArr.frontDoor.clampWhenFinished = true; clipActionArr.frontDoor.play(); }; break; case 2: // 关闭前门 handler = () => { clipActionArr.frontDoor.paused = true; clipActionArr.frontDoor.reset(); // clipActionArr.frontDoor.time = 4; clipActionArr.frontDoor.timeScale = -0.01; clipActionArr.frontDoor.clampWhenFinished = true; clipActionArr.frontDoor.play(); }; break; case 3: // 打开后门 handler = () => { clipActionArr.backDoor.paused = true; clipActionArr.backDoor.reset(); clipActionArr.backDoor.time = 0.5; clipActionArr.backDoor.timeScale = 0.01; clipActionArr.backDoor.clampWhenFinished = true; clipActionArr.backDoor.play(); }; break; case 4: // 关闭后门 handler = () => { clipActionArr.backDoor.paused = true; clipActionArr.backDoor.reset(); clipActionArr.backDoor.time = 4; clipActionArr.backDoor.timeScale = -0.01; clipActionArr.backDoor.clampWhenFinished = true; clipActionArr.backDoor.play(); }; break; case 5: // 打开前后门 handler = () => { clipActionArr.backDoor.paused = true; clipActionArr.frontDoor.paused = true; clipActionArr.frontDoor.reset(); clipActionArr.frontDoor.time = 0.5; clipActionArr.frontDoor.timeScale = 0.01; clipActionArr.frontDoor.clampWhenFinished = true; clipActionArr.frontDoor.play(); clipActionArr.backDoor.reset(); clipActionArr.backDoor.time = 0.5; clipActionArr.backDoor.timeScale = 0.01; clipActionArr.backDoor.clampWhenFinished = true; clipActionArr.backDoor.play(); }; break; case 6: // 关闭前后门 handler = () => { clipActionArr.backDoor.paused = true; clipActionArr.frontDoor.paused = true; clipActionArr.frontDoor.reset(); clipActionArr.frontDoor.time = 4; clipActionArr.frontDoor.timeScale = -0.01; clipActionArr.frontDoor.clampWhenFinished = true; clipActionArr.frontDoor.play(); clipActionArr.backDoor.reset(); clipActionArr.backDoor.time = 4; clipActionArr.backDoor.timeScale = -0.01; clipActionArr.backDoor.clampWhenFinished = true; clipActionArr.backDoor.play(); }; break; default: } handler(); model.clock.start(); // const honglvdeng = group.getObjectByName('honglvdeng'); // const material = honglvdeng.material; // setTimeout(() => { // if (handlerState === 2 || handlerState === 4 || handlerState === 6) { // material.color = new THREE.Color(0x00ff00); // } else { // material.color = new THREE.Color(0xff0000); // } // }, 1000); }; // 初始化门的开关状态 export const initOpenState = (selectData) => { if (!group) return; model.camera.position.set(-1000, 100, 500); group.rotation.y = 0; return new Promise(async (resolve) => { setTimeout(async () => { const oldCameraPosition = { x: -1000, y: 100, z: 500 }; await animateCamera(oldCameraPosition, oldCameraPosition, { x: 46.257, y: 57.539, z: 94.313 }, { x: -50, y: 0, z: 0 }, model, 0.8); resolve(null); if (!selectData) { return; } if (selectData.frontGateOpen == 1) { clipActionArr.frontDoor.reset(); clipActionArr.frontDoor.time = 0.5; clipActionArr.frontDoor.clampWhenFinished = true; clipActionArr.frontDoor.timeScale = 1; clipActionArr.frontDoor.play(); } else { clipActionArr.frontDoor.reset(); clipActionArr.frontDoor.time = 4; clipActionArr.frontDoor.timeScale = -1; clipActionArr.frontDoor.clampWhenFinished = true; clipActionArr.frontDoor.play(); } if (selectData.rearGateOpen == 1) { clipActionArr.backDoor.reset(); clipActionArr.backDoor.time = 0.5; clipActionArr.backDoor.timeScale = 1; clipActionArr.backDoor.clampWhenFinished = true; clipActionArr.backDoor.play(); } else { clipActionArr.backDoor.reset(); clipActionArr.backDoor.time = 4; clipActionArr.backDoor.timeScale = -1; clipActionArr.backDoor.clampWhenFinished = true; clipActionArr.backDoor.play(); } model.clock.start(); }, 800); }); }; export const mountedThree = (playerVal1, playerVal2) => { return new Promise((resolve) => { model = new UseThree('#damper3D', '', '#deviceDetail'); model.setEnvMap('test1'); model.renderer.toneMappingExposure = 0.8; model.setModel(modelName).then((gltf) => { group = gltf.scene; group.layers.enableAll(); 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); }); } model.scene?.add(group); // model.camera.position.set(-1000, 100, 500); addLight(model.scene); // resetCamera(); setModalPosition(); startAnimation(); // 初始化左右摇摆动画; // startAnimation(); initAnimation(); model.animate(); drawHots(); deviceDetailCard(); if (model.camera.layers.mask == -1) model.camera.layers.toggle(1); // renderBloomPass = createComposer(model).renderBloomPass; // const flyLineMesh = flyLine( // [ // new THREE.Vector3(-110, 0, 0), // // new THREE.Vector3(5, 4, 0), // new THREE.Vector3(120, 0, 0), // ], // '/model/hdr/y1.png' // ); // group.add(flyLineMesh); setTimeout(async () => { player1 = playerVal1; player2 = playerVal2; const videoPlayer1 = document.getElementById('fm-player1')?.getElementsByClassName('vjs-tech')[0]; const videoPlayer2 = document.getElementById('fm-player2')?.getElementsByClassName('vjs-tech')[0]; if (videoPlayer1) { const mesh = renderVideo(group, videoPlayer1, 'player1'); mesh.scale.set(-0.028, 0.0285, 1); mesh.position.set(4.298, 0.02, -0.4); mesh.rotation.y = -Math.PI; group.add(mesh); } if (videoPlayer2) { const mesh = renderVideo(group, videoPlayer2, 'player2'); mesh.scale.set(-0.028, 0.0285, 1); mesh.position.set(-4.262, 0.02, -0.4); mesh.rotation.y = -Math.PI; group.add(mesh); } resolve(model); }, 0); }); }); }; export const destroy = () => { if (model) { if (model.mixers[0]) { model.mixers[0].uncacheClip(clipActionArr.frontDoor.getClip()); model.mixers[0].uncacheClip(clipActionArr.backDoor.getClip()); model.mixers[0].uncacheAction(clipActionArr.frontDoor, group); model.mixers[0].uncacheAction(clipActionArr.backDoor, group); model.mixers[0].uncacheRoot(group); model.animations[0].tracks = []; } clipActionArr.backDoor = undefined; clipActionArr.frontDoor = undefined; model.mixers = []; model.deleteModal(); model = null; group = null; // document.getElementById('damper3D').parentElement.remove(document.getElementById('damper3D')) } };