import * as THREE from 'three'; // 导入轨道控制器 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer.js'; import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer.js'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; // import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'; import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'; import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; import Stats from 'three/examples/jsm/libs/stats.module.js'; import { useModelStore } from '/@/store/modules/threejs'; import TWEEN from 'three/examples/jsm/libs/tween.module.js'; import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'; import { useGlobSetting } from '/@/hooks/setting'; import { getList } from '@/views/vent/sys/resources/file.api'; import { saveModel } from '/@/utils/threejs/util'; const globSetting = useGlobSetting(); const baseApiUrl = globSetting.domainUrl; // import * as dat from 'dat.gui'; // const gui = new dat.GUI(); // gui.domElement.style = 'position:absolute;top:10px;left:10px;z-index:99999999999999'; class UseThree { stats: Stats | null = null; // 帧 canvasContainer: HTMLCanvasElement | null; //canvas 容器 CSSCanvasContainer: HTMLCanvasElement | null = null; // css canvas 容器 camera: THREE.PerspectiveCamera | null = null; // 相机 scene: THREE.Scene | null = null; renderer: THREE.WebGLRenderer | null = null; renderEnabled = true; css3dRender: CSS3DRenderer | null = null; css2dRender: CSS2DRenderer | null = null; orbitControls: OrbitControls | null = null; animationAction: THREE.AnimationAction | null = null; clock: THREE.Clock | null = new THREE.Clock(); // 计时器 timeoutId: NodeJS.Timeout | null = null; animationId = 0; isRender = true; spriteText: THREE.Sprite | null = null; mouse = new THREE.Vector2(); rayCaster: THREE.Raycaster | null = null; animations: THREE.AnimationClip[] = []; mixers: THREE.AnimationMixer[] = []; renderT = 1 / 40; timeS = 0; track: any = null; composer; //后期 timeOut: NodeJS.Timeout | null = null; // textureMap = new Map(); constructor(canvasSelector, css3Canvas?, css2Canvas?) { this.canvasContainer = document.querySelector(canvasSelector); //初始化 this.init(css3Canvas, css2Canvas); // this.animate(); window.addEventListener('resize', this.resizeRenderer.bind(this)); // 添加滚动事件,鼠标滚动模型执行动画 // window.addEventListener('wheel', this.wheelRenderer.bind(this)); // this.canvasContainer?.appendChild(gui.domElement); } init(css3Canvas?, css2Canvas?) { // 初始化场景 this.initScene(); // 初始化环境光 // this.initLight(); // 初始化相机 this.initCamera(); //初始化渲染器 this.initRenderer(); // 初始化控制器 this.initControles(); if (css3Canvas) { this.initCSS3Renderer(css3Canvas); } if (css2Canvas) { this.initCSS2Renderer(css2Canvas); } // this.setTestPlane(); this.rayCaster = new THREE.Raycaster(); // this.createStats(); // this.removeSawtooth(); } createStats() { this.stats = Stats(); this.stats?.setMode(0); this.stats.domElement.style = 'position: absolute; top: 300px'; this.canvasContainer?.appendChild(this.stats.domElement); } initScene() { this.scene = new THREE.Scene(); // const axesHelper = new THREE.AxesHelper(100); // this.scene?.add(axesHelper); // const size = 1000; // const divisions = 10; // const gridHelper = new THREE.GridHelper(size, divisions); // this.scene?.add(gridHelper); } initLight() { // const light = new THREE.AmbientLight(0xffffff, 1); // light.position.set(0, 1000, 1000); // (this.scene as THREE.Scene).add(light); } initCamera() { // this.camera = new THREE.PerspectiveCamera(50, this.canvasContainer.clientWidth / this.canvasContainer.clientHeight, 0.0000001, 1000); if (!window['$camera']) { throw new Error('threejs摄像头初始化异常!'); } else { this.camera = window['$camera'] as THREE.PerspectiveCamera; this.camera.layers.enableAll(); if (this.canvasContainer) this.camera.aspect = this.canvasContainer.clientWidth / this.canvasContainer.clientHeight; this.camera.near = 0.0000001; this.camera.far = 1000; } // // const helper = new THREE.CameraHelper(this.camera); // this.scene?.add(helper); // gui.add(this.camera.position, 'x', 0.00001, 10000); // gui.add(this.camera.position, 'y', 0.00001, 10000); // gui.add(this.camera.position, 'z', 0.00001, 10000); // gui.add(this.camera, 'near', 0.01, 1).step(0.01); // gui.add(this.camera, 'far', 10, 100000); // gui.add(this.camera, 'fov', 0, 180); } initRenderer() { if (!window['$renderer']) { throw new Error('threejs渲染器初始化异常!'); } else { this.renderer = window['$renderer']; if (this.canvasContainer) { this.renderer.toneMappingExposure = 1.0; this.renderer.toneMapping = THREE.ACESFilmicToneMapping; // const gl = this.renderer?.getContext('webgl'); // gl && gl.getExtension('WEBGL_lose_context')?.restoreContext(); // this.renderer?.forceContextRestore() this.renderer?.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); this.canvasContainer.appendChild(this.renderer.domElement); } } } initCSS3Renderer(cssCanvas) { this.CSSCanvasContainer = document.querySelector(cssCanvas); if (this.CSSCanvasContainer) { this.css3dRender = new CSS3DRenderer() as CSS3DRenderer; this.css3dRender.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); this.CSSCanvasContainer?.appendChild(this.css3dRender.domElement); this.css3dRender.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera); // this.css3dRender.domElement.style.pointerEvents = 'none'; // this.orbitControls = new OrbitControls(this.camera as THREE.Camera, this.css3dRender?.domElement) as OrbitControls; // this.orbitControls.update(); } } initCSS2Renderer(cssCanvas) { this.CSSCanvasContainer = document.querySelector(cssCanvas); if (this.CSSCanvasContainer) { this.css2dRender = new CSS2DRenderer(); this.css2dRender.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); this.CSSCanvasContainer?.appendChild(this.css2dRender.domElement); this.css2dRender.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera); // this.orbitControls = new OrbitControls(this.camera as THREE.Camera, this.css2dRender?.domElement) as OrbitControls; // this.orbitControls.update(); // this.css2dRender.domElement.style.pointerEvents = 'none'; } } initControles() { if (!window['$orbitControls']) { throw new Error('threejs控制器初始化异常!'); } else { this.orbitControls = window['$orbitControls']; this.orbitControls.panSpeed = 1; this.orbitControls.rotateSpeed = 1; this.orbitControls.maxPolarAngle = Math.PI; this.orbitControls.minPolarAngle = 0; } // this.orbitControls = new OrbitControls(this.camera as THREE.Camera, this.renderer?.domElement) as OrbitControls; // this.orbitControls.update(); // this.orbitControls.minDistance = 1; // this.orbitControls.maxDistance = 100; // this.orbitControls.maxDistance = true; } setGLTFModel(modalNames, group = null, isBlender = false) { window['startTime'] = new Date().getTime(); const modelStore = useModelStore(); return new Promise(async (resolve, reject) => { try { const gltfLoader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('/model/draco/gltf/'); dracoLoader.setDecoderConfig({ type: 'js' }); //使用兼容性强的draco_decoder.js解码器 dracoLoader.preload(); gltfLoader.setDRACOLoader(dracoLoader); const db = window['CustomDB']; const resolvePromise: Promise[] = []; const modalNameArr = Object.prototype.toString.call(modalNames) === '[object Array]' ? modalNames : [modalNames]; const len = modalNameArr.length; for (let i = 0; i < len; i++) { resolvePromise[i] = new Promise(async (childResolve, reject) => { try { // 解析模型 const modalNameStr = modalNameArr[i]; const data = modelStore.modelArr.get(modalNameStr) || null; let modalValue; if (!data) { const modalArr = await db.modal.where('modalName').equals(modalNameStr).toArray(); if (modalArr.length > 0) { modalValue = modalArr[0].modalVal; } } else { modalValue = data.modalVal; } if (modalValue) { gltfLoader.parse( modalValue, '/model/glft/', (gltf) => { let object: THREE.Object3D = gltf.scene; // setModalCenter(object); object.traverse((obj) => { if (obj instanceof THREE.Mesh) { obj.material.emissiveIntensity = 1; obj.material.emissiveMap = obj.material.map; obj.material.blending = THREE.CustomBlending; if (obj.material.opacity < 1) { obj.material.transparent = true; } if (obj.material.map) { obj.material.map.colorSpace = THREE.SRGBColorSpace; obj.material.map.flipY = false; obj.material.map.anisotropy = 1; } if (obj.material.emissiveMap) { obj.material.emissiveMap.colorSpace = THREE.SRGBColorSpace; obj.material.emissiveMap.flipY = false; } if (obj.material.map || obj.material.emissiveMap) { obj.material.needsUpdate = true; } // if (envMap) { // obj.material.envMap = envMap; // obj.material.envMapIntensity = 1; // } // obj.renderOrder = 1; } }); if (isBlender) { object = object.children[0]; } object.animations = gltf.animations; object.name = modalNameStr; if (group) group.add(object); childResolve(object); }, (err) => { console.log(err); } ); } else { // 开启线程下载 console.log('需要开启线程下载模型资源。。。。。'); const result = (await getList({ fileName: modalNameStr })) || []; const file = result['records'][0]; if (file && file.path) { gltfLoader.load(`${baseApiUrl}/sys/common/static/${file.path}`, async (glft) => { if (glft) { let object: THREE.Object3D = glft.scene; if (isBlender) { object = object.children[0]; } object.name = modalNameStr; if (glft.animations.length > 0) { object.animations = glft.animations; } if (group) group.add(object); childResolve(object); const modalArr = await db.modal.where('modalName').equals(modalNameStr).toArray(); if (modalArr.length < 1) { saveModel(modalNameStr, file.path, file.version); } } }); } else { childResolve(null); } } } catch (error) { console.log(error); reject(); } }); } Promise.all(resolvePromise).then((objects) => { dracoLoader.dispose(); resolve(objects); }); } catch (error) { reject('加载模型出错'); } }); } // setFBXModel(modalNames, group = null) { // window['startTime'] = new Date().getTime(); // const modelStore = useModelStore(); // return new Promise(async (resolve, reject) => { // try { // const fbxLoader = new FBXLoader(); // fbxLoader.setPath('/model/fbx/'); // const db = window['CustomDB']; // const resolvePromise: Promise[] = []; // let modalNameArr = Object.prototype.toString.call(modalNames) === '[object Array]' ? modalNames : [modalNames]; // let len = modalNameArr.length; // for (let i = 0; i < len; i++) { // resolvePromise[i] = new Promise(async (childResolve, reject) => { // try { // // 解析模型 // let modalValue; // const modalNameStr = modalNameArr[i]; // let data = modelStore.modelArr.get(modalNameStr) || null; // if (!data) { // const modalArr = await db.modal.where('modalName').equals(modalNameStr).toArray(); // if (modalArr.length > 0) modalValue = modalArr[0].modalVal; // } else { // modalValue = data.modalVal; // } // if (modalValue) { // const object = fbxLoader.parse(modalValue, '/model/fbx/'); // // const object = fbx.scene; // // setModalCenter(object); // if (object) { // object.traverse((obj) => { // if (obj instanceof THREE.Mesh) { // obj.material.emissiveIntensity = 1; // obj.material.emissiveMap = obj.material.map; // obj.material.blending = THREE.CustomBlending; // if (obj.material.opacity < 1) { // obj.material.transparent = true; // } // if (obj.material.map) { // obj.material.map.encoding = THREE.sRGBEncoding; // obj.material.map.flipY = false; // obj.material.map.anisotropy = 1; // } // if (obj.material.emissiveMap) { // obj.material.emissiveMap.encoding = THREE.sRGBEncoding; // obj.material.emissiveMap.flipY = false; // } // if (obj.material.map || obj.material.emissiveMap) { // obj.material.needsUpdate = true; // } // // if (envMap) { // // obj.material.envMap = envMap; // // obj.material.envMapIntensity = 1; // // } // // obj.renderOrder = 1; // } // }); // object.animations = object.animations; // object.name = modalNameStr; // group?.add(object); // childResolve(object); // } // } // } catch (error) { // console.log(error); // reject(); // } // }); // } // Promise.all(resolvePromise).then((objects) => { // resolve(objects); // }); // } catch (error) { // reject('加载模型出错'); // } // }); // } setTestPlane() { const plane = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshPhongMaterial({ color: 0x999999, specular: 0x101010 })); plane.rotation.x = -Math.PI / 2; plane.position.y = 0.03; plane.receiveShadow = true; this.scene?.add(plane); } removeSawtooth() { this.composer = new EffectComposer(this.renderer as THREE.WebGLRenderer); const FXAAShaderPass = new ShaderPass(FXAAShader); FXAAShaderPass.uniforms['resolution'].value.set(1 / this.canvasContainer.clientWidth, 1 / this.canvasContainer.clientHeight); FXAAShaderPass.renderToScreen = true; this.composer.addPass(FXAAShaderPass); } /* 场景环境背景 */ setEnvMap(hdr) { if (!this.scene) return; // (this.scene as THREE.Scene).environment new RGBELoader().setPath('/model/hdr/').load(hdr, (texture) => { texture.colorSpace = THREE.SRGBColorSpace; texture.mapping = THREE.EquirectangularReflectionMapping; if (this.scene) (this.scene as THREE.Scene).environment = texture; texture.dispose(); }); } render() { this.startAnimation(); this.orbitControls?.update(); this.camera?.updateMatrixWorld(); // this.composer?.render(); this.renderer?.render(this.scene as THREE.Object3D, this.camera as THREE.Camera); this.css3dRender?.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera); this.css2dRender?.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera); } timeRender() { //设置为可渲染状态 this.renderEnabled = true; //清除上次的延迟器 if (this.timeOut) { clearTimeout(this.timeOut); } this.timeOut = setTimeout(() => { this.renderEnabled = false; }, 3000); } /* 漫游 */ startMY() {} /* 初始动画 */ startAnimation() {} renderAnimationScene() {} animate() { if (this.isRender) { this.animationId = requestAnimationFrame(this.animate.bind(this)); const T = this.clock?.getDelta() || 0; this.timeS = this.timeS + T; if (this.timeS > this.renderT) { this.renderAnimationScene(); if (this.renderEnabled) { this.render(); } this.stats?.update(); this.timeS = 0; } // this.stats?.update(); // // TWEEN.update(); // // this.renderAnimationScene(); // if (this.renderEnabled) { // this.render(); // } } } resizeRenderer() { // 更新相机比例 if (this.camera) { (this.camera as THREE.PerspectiveCamera).aspect = this.canvasContainer.clientWidth / this.canvasContainer.clientHeight; // 刷新相机矩阵 this.camera?.updateProjectionMatrix(); } // 设置场景尺寸 this.renderer?.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); this.css3dRender?.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); this.css2dRender?.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); } wheelRenderer(e: WheelEvent) { const timeScale = e.deltaY > 0 ? 1 : -1; this.animationAction?.setEffectiveTimeScale(timeScale); (this.animationAction as THREE.AnimationAction).paused = false; this.animationAction?.play(); if (this.timeoutId) { clearTimeout(this.timeoutId); } this.timeoutId = setTimeout(() => { this.animationAction?.halt(0.5); }, 500); } clearMesh(item) { item.geometry?.dispose(); if (item.material) { const material = item.material; for (const key of Object.keys(material)) { const value = material[key]; if (value && typeof value === 'object' && value['dispose'] && typeof value['dispose'] === 'function') { value.dispose(); } } material.dispose(); } if (item.texture) { item.texture.dispose(); } } clearGroup(group) { const removeObj = (obj) => { if (obj && obj?.children && obj?.children.length > 0) { for (let i = obj?.children.length - 1; i >= 0; i--) { const item = obj?.children[i]; if (item && item.children && item.children.length > 0) { removeObj(item); item?.clear(); } else { if (item) { if (item.parent) item.parent.remove(item); this.clearMesh(item); item.clear(); } } } } }; removeObj(group); } clearScene() { this.clearGroup(this.scene); // console.log('场景纹理数量----------->', this.textureMap.size); this.textureMap.forEach((texture) => { texture?.dispose(); }); this.textureMap.clear(); } destroy() { TWEEN.getAll().forEach((item) => { item.stop(); }); TWEEN.removeAll(); this.isRender = false; cancelAnimationFrame(this.animationId); this.clearScene(); window.removeEventListener('resize', this.resizeRenderer); // this.orbitControls?.dispose(); // this.scene?.environment?.dispose(); this.scene?.clear(); this.renderer?.dispose(); this.renderer?.getRenderTarget()?.dispose(); if (this.renderer && this.canvasContainer) this.canvasContainer.innerHTML = ''; if (this.CSSCanvasContainer && this.css3dRender) this.CSSCanvasContainer.innerHTML = ''; // if (this.renderer) this.renderer.domElement = null; this.renderer?.clear(); if (this.css3dRender) this.css3dRender.domElement = null; if (this.css2dRender) this.css2dRender.domElement = null; if (this.canvasContainer) this.canvasContainer.innerHTML = ''; if (this.CSSCanvasContainer) this.CSSCanvasContainer.innerHTML = ''; this.camera = null; this.orbitControls = null; this.renderer = null; this.stats = null; this.scene = null; this.css3dRender = null; this.css2dRender = null; THREE.Cache.clear(); console.log('场景销毁后信息----------->', window['$renderer']?.info, this.scene); } } export default UseThree;