import * as THREE from 'three'; import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js'; import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'; import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; import { TWEEN } from 'three/examples/jsm/libs/tween.module.min.js'; import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'; import gsap from 'gsap'; import { useAppStore } from '/@/store/modules/app'; import UseThree from './useThree'; // import * as dat from "dat.gui"; /* 设置模型居中 */ export const setModalCenter = (group, modal?) => { const box3 = new THREE.Box3(); // 计算层级模型group的包围盒 // 模型group是加载一个三维模型返回的对象,包含多个网格模型 box3.expandByObject(group); // 计算一个层级模型对应包围盒的几何体中心在世界坐标中的位置 const center = new THREE.Vector3(); box3.getCenter(center); // console.log('查看几何体中心坐标', center); // 重新设置模型的位置,使之居中。 group.position.x = group.position.x - center.x; group.position.y = group.position.y - center.y; group.position.z = group.position.z - center.z; }; // 获取一个canvas 图文纹理 export const getTextCanvas = (w, h, textArr, imgUrl) => { // canvas 宽高最好是2的倍数 const width = w; const height = h; // 创建一个canvas元素 获取上下文环境 const canvas = document.createElement('canvas'); canvas.style.letterSpacing = 10 + 'px'; const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; canvas.width = width; canvas.height = height; // 设置样式 ctx.textAlign = 'start'; ctx.fillStyle = 'rgba(0, 0, 0, 0)'; // 创建渐变 // var gradient=ctx.createLinearGradient(0,0, canvas.width,0); // gradient.addColorStop(0,"magenta"); // gradient.addColorStop(0.5,"blue"); // gradient.addColorStop(1.0,"red"); // // 用渐变填色 // ctx.fillStyle=gradient; ctx.shadowColor = 'rgba(0, 10,0,0.8)'; ctx.shadowBlur = 4; ctx.shadowOffsetX = 1; ctx.shadowOffsetY = 1; ctx.fillRect(0, 0, width, height); //添加背景图片,进行异步,否则可能会过早渲染,导致空白 return new Promise((resolve, reject) => { if (imgUrl) { const img = new Image(); img.src = new URL('../../assets/images/vent/model_image/' + imgUrl, import.meta.url).href; img.onload = () => { //将画布处理为透明 ctx.clearRect(0, 0, width, height); //绘画图片 ctx.drawImage(img, 0, 0, width, height); ctx.textBaseline = 'middle'; // 由于文字需要自己排版 所以循环一下 // item 是自定义的文字对象 包含文字内容 字体大小颜色 位置信息等 textArr.forEach((item) => { ctx.font = item.font; ctx.fillStyle = item.color; ctx.fillText(item.text, item.x, item.y, 1024); }); resolve(canvas); }; //图片加载失败的方法 img.onerror = (e) => { reject(e); }; } else { //将画布处理为透明 ctx.clearRect(0, 0, width, height); ctx.textBaseline = 'middle'; textArr.forEach((item) => { ctx.lineWidth = 2; ctx.font = item.font; ctx.fillStyle = item.color; // !!item.strokeStyle && (ctx.strokeStyle = item.strokeStyle); // if (item.strokeStyle) ctx.strokeText(item.text, item.x, item.y, 1024); ctx.fillText(item.text, item.x, item.y, 1024); }); resolve(canvas); } }); }; // 发光路径 export const setLineGeo = (scene) => { const box = new THREE.BoxGeometry(30, 30, 30); // 立方体几何体box作为EdgesGeometry参数创建一个新的几何体 const edges = new THREE.EdgesGeometry(box); // 立方体线框,不显示中间的斜线 new THREE.TextureLoader().setPath('/model/hdr/').load('8.png', (texture) => { const edgesMaterial = new THREE.MeshBasicMaterial({ // color: 0x00ffff, map: texture, transparent: true, depthWrite: false, }); const line = new THREE.LineSegments(edges, edgesMaterial); // 网格模型和网格模型对应的轮廓线框插入到场景中 scene.add(line); }); box.attributes.position.array; const lightMaterial = new THREE.ShaderMaterial({ vertexShader: `varying vec3 vPosition; varying vec2 vUv; uniform float uTime; void main(){ // vec3 scalePosition = vec3(position.x+uTime,position.y,position.z+uTime); vec4 viewPosition = viewMatrix * modelMatrix * vec4(position,1); gl_Position = projectionMatrix * viewPosition; vPosition = position; vUv = uv; }`, fragmentShader: `varying vec3 vPosition; varying vec2 vUv; uniform vec3 uColor; uniform float uHeight; vec4 permute(vec4 x) { return mod(((x*34.0)+1.0)*x, 289.0); } vec2 fade(vec2 t) { return t*t*t*(t*(t*6.0-15.0)+10.0); } float cnoise(vec2 P) { vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0); vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0); Pi = mod(Pi, 289.0); // To avoid truncation effects in permutation vec4 ix = Pi.xzxz; vec4 iy = Pi.yyww; vec4 fx = Pf.xzxz; vec4 fy = Pf.yyww; vec4 i = permute(permute(ix) + iy); vec4 gx = 2.0 * fract(i * 0.0243902439) - 1.0; // 1/41 = 0.024... vec4 gy = abs(gx) - 0.5; vec4 tx = floor(gx + 0.5); gx = gx - tx; vec2 g00 = vec2(gx.x,gy.x); vec2 g10 = vec2(gx.y,gy.y); vec2 g01 = vec2(gx.z,gy.z); vec2 g11 = vec2(gx.w,gy.w); vec4 norm = 1.79284291400159 - 0.85373472095314 * vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)); g00 *= norm.x; g01 *= norm.y; g10 *= norm.z; g11 *= norm.w; float n00 = dot(g00, vec2(fx.x, fy.x)); float n10 = dot(g10, vec2(fx.y, fy.y)); float n01 = dot(g01, vec2(fx.z, fy.z)); float n11 = dot(g11, vec2(fx.w, fy.w)); vec2 fade_xy = fade(Pf.xy); vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x); float n_xy = mix(n_x.x, n_x.y, fade_xy.y); return 2.3 * n_xy; } void main(){ float strength = (vPosition.y+uHeight/2.0)/uHeight; gl_FragColor = vec4(uColor,1.0 - strength); // float strength =1.0 - abs(cnoise(vUv * 10.0)) ; // gl_FragColor =vec4(strength,strength,strength,1); }`, transparent: true, side: THREE.DoubleSide, }); const lightMesh = new THREE.Mesh(box, lightMaterial); lightMesh.geometry.computeBoundingBox(); const { min, max } = lightMesh.geometry.boundingBox; const uHeight = max.y - min.y; lightMaterial.uniforms.uHeight = { value: uHeight, }; lightMaterial.uniforms.uColor = { value: new THREE.Color(0x00ff00), }; lightMaterial.uniforms.uTime = { value: 0, }; // gsap.to(lightMesh.scale, { // // x: 2, // // z: 2, // y: 0.8, // duration: 1, // ease: 'none', // repeat: -1, // yoyo: true, // }); scene.add(lightMesh); }; export const setOutline = (model, group) => { const { scene, renderer, camera } = model; const params = { edgeStrength: 10.0, edgeGlow: 1, edgeThickness: 1.0, pulsePeriod: 5, rotate: false, usePatternTexture: false, }; const composer = new EffectComposer(renderer); const renderPass = new RenderPass(scene, camera); composer.addPass(renderPass); const outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera); composer.addPass(outlinePass); outlinePass.visibleEdgeColor.set(parseInt(0xffffff)); outlinePass.hiddenEdgeColor.set('#190a05'); outlinePass.edgeStrength = params.edgeStrength; outlinePass.edgeThickness = params.edgeThickness; outlinePass.pulsePeriod = params.pulsePeriod; outlinePass.usePatternTexture = params.usePatternTexture; // const textureLoader = new THREE.TextureLoader(); // textureLoader.load('model/hdr/tri_pattern.jpg', function (texture) { // outlinePass.patternTexture = texture; // texture.wrapS = THREE.RepeatWrapping; // texture.wrapT = THREE.RepeatWrapping; // }); const effectFXAA = new ShaderPass(FXAAShader); effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight); composer.addPass(effectFXAA); const scale = 1; group.traverse(function (child) { if (child instanceof THREE.Mesh) { // child.geometry.center(); child.geometry.computeBoundingSphere(); } }); // group.scale.multiplyScalar(Math.random() * 0.3 + 0.1); group.scale.divideScalar(scale); return { outlinePass, composer }; }; /* 渲染视频 */ export const renderVideo = (group, player, playerMeshName) => { //加载视频贴图; const texture = new THREE.VideoTexture(player); if (texture && group?.getObjectByName(playerMeshName)) { const player = group.getObjectByName(playerMeshName); player.material.map = texture; } else { //创建网格; const planeGeometry = new THREE.PlaneGeometry(30, 20); const material = new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide, }); /* 消除摩尔纹 */ texture.magFilter = THREE.LinearFilter; texture.minFilter = THREE.LinearFilter; texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping; texture.format = THREE.RGBAFormat; texture.anisotropy = 0.5; // texture.generateMipmaps = false const mesh = new THREE.Mesh(planeGeometry, material); mesh.name = playerMeshName; // group.add(mesh); return mesh; } }; // oldP 相机原来的位置 // oldT target原来的位置 // newP 相机新的位置 // newT target新的位置 // callBack 动画结束时的回调函数 export const animateCamera = (oldP, oldT, newP, newT, model, duration = 0.5, callBack?) => { return new Promise((resolve) => { const camera = model.camera; const controls = model.orbitControls; controls.enabled = false; controls.target.set(0, 0, 0); const animateObj = { x1: oldP.x, // 相机x y1: oldP.y, // 相机y z1: oldP.z, // 相机z x2: oldT.x, // 控制点的中心点x y2: oldT.y, // 控制点的中心点y z2: oldT.z, // 控制点的中心点z }; gsap.fromTo( animateObj, { x1: oldP.x, // 相机x y1: oldP.y, // 相机y z1: oldP.z, // 相机z x2: oldT.x, // 控制点的中心点x y2: oldT.y, // 控制点的中心点y z2: oldT.z, // 控制点的中心点z }, { x1: newP.x, y1: newP.y, z1: newP.z, x2: newT.x, y2: newT.y, z2: newT.z, duration: duration, ease: 'easeOutBounce', onUpdate: function (object) { // 这里写逻辑 camera.position.set(object.x1, object.y1, object.z1); controls.target.set(object.x2, object.y2, object.z2); controls.update(); if (callBack) callBack(); }, onUpdateParams: [animateObj], onComplete: function () { // 完成 controls.enabled = true; resolve(null); }, } ); }); }; export const transScreenCoord = (vector, camera) => { // const screenCoord = { x: 0, y: 0 }; // vector.project(camera); // screenCoord.x = (0.5 + vector.x / 2) * window.innerWidth; // screenCoord.y = (0.5 - vector.y / 2) * window.innerHeight; // return screenCoord; const stdVector = vector.project(camera); const a = window.innerWidth / 2; const b = window.innerHeight / 2; const x = Math.round(stdVector.x * a + a); const y = Math.round(-stdVector.y * b + b); return { x, y }; }; export const drawHot = (scale: number) => { // const hotMap = new THREE.TextureLoader().load('/src/assets/images/hot-point.png'); // const hotMap = new THREE.TextureLoader().setPath('/model/img/').load('/hot-point.png'); const hotMap = new THREE.TextureLoader().load('/model/img/hot-point.png'); const material = new THREE.SpriteMaterial({ map: hotMap, }); const hotPoint = new THREE.Sprite(material); const spriteTween = new TWEEN.Tween({ scale: 1 * scale, }) .to( { scale: 0.65 * scale, }, 1000 ) .easing(TWEEN.Easing.Quadratic.Out); spriteTween.onUpdate(function (that) { hotPoint.scale.set(that.scale, that.scale, that.scale); }); spriteTween.yoyo(true); spriteTween.repeat(Infinity); spriteTween.start(); return hotPoint; }; export const deviceDetailCard = () => { // }; export const updateAxisCenter = (modal: UseThree, group: THREE.Object3D, event, callBack?) => { if (!modal) return; const appStore = useAppStore(); event.stopPropagation(); const widthScale = appStore.getWidthScale; const heightScale = appStore.getHeightScale; // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1) modal.mouse.x = ((-modal.canvasContainer.getBoundingClientRect().left * widthScale + event.clientX) / (modal.canvasContainer.clientWidth * widthScale)) * 2 - 1; modal.mouse.y = -((-modal.canvasContainer.getBoundingClientRect().top + event.clientY) / (modal.canvasContainer.clientHeight * heightScale)) * 2 + 1; (modal.rayCaster as THREE.Raycaster).setFromCamera(modal.mouse, modal.camera as THREE.Camera); if (group) { const intersects = modal.rayCaster?.intersectObjects(group.children, true) as THREE.Intersection[]; if (intersects.length > 0) { const point = intersects[0].point; const target0 = modal.orbitControls.target.clone(); gsap.fromTo( modal.orbitControls.target, { x: target0.x, y: target0.y, z: target0.z }, { x: point.x, y: point.y, z: point.z, duration: 0.4, ease: 'easeInCirc', onUpdate: function (object) { if (object) modal.camera?.lookAt(new THREE.Vector3(object.x, object.y, object.z)); }, } ); callBack(intersects); } } // const factor = 1; // //这里定义深度值为0.5,深度值越大,意味着精度越高 // var vector = new THREE.Vector3(modal.mouse.x, modal.mouse.y, 0.5); // //将鼠标坐标转换为3D空间坐标 // vector.unproject(modal.camera); // //获得从相机指向鼠标所对应的3D空间点的射线(归一化) // vector.sub(modal.camera.position).normalize(); // if (event.originalEvent && event.originalEvent.deltaY && event.originalEvent.deltaY < 0) { // modal.camera.position.x += vector.x * factor; // modal.camera.position.y += vector.y * factor; // modal.camera.position.z += vector.z * factor; // modal.orbitControls.target.x += vector.x * factor; // modal.orbitControls.target.y += vector.y * factor; // modal.orbitControls.target.z += vector.z * factor; // } else { // modal.camera.position.x -= vector.x * factor; // modal.camera.position.y -= vector.y * factor; // modal.camera.position.z -= vector.z * factor; // modal.orbitControls.target.x -= vector.x * factor; // modal.orbitControls.target.y -= vector.y * factor; // modal.orbitControls.target.z -= vector.z * factor; // } // modal.orbitControls.update(); // modal.camera.updateMatrixWorld(); }; export const addEnvMap = (hdr, modal) => { return new Promise((resolve) => { new RGBELoader().setPath('/model/hdr/').load(hdr + '.hdr', (texture) => { texture.mapping = THREE.EquirectangularReflectionMapping; const defaultEnvironment = texture; modal.scene.environment = defaultEnvironment; resolve(texture); }); }); };