import * as THREE from 'three'; import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'; // 导入轨道控制器 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer' // import gsap from 'gsap'; import ResourceTracker from '/@/utils/threejs/ResourceTracker'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader' class UseThree { canvasContainer: HTMLCanvasElement; CSSCanvasContainer: HTMLCanvasElement | null = null; camera: THREE.PerspectiveCamera | null = null; scene: THREE.Scene | null = null; renderer: THREE.WebGLRenderer | null = null; css3dRender: CSS3DRenderer | null = null; orbitControls: OrbitControls | null = null; giftLoader: THREE.Object3D | null = null; animationMixer: THREE.AnimationMixer | null = null; animationAction: THREE.AnimationAction | null = null; clock: THREE.Clock = new THREE.Clock(); // 计时器 timeoutId: NodeJS.Timeout | null = null; animationId: number = 0 resourceTracker:ResourceTracker | null = null track: any = null spriteText: THREE.Sprite | null = null constructor(canvasSelector, cssCanvas?) { this.resourceTracker = new ResourceTracker() this.track = this.resourceTracker.track.bind(this.resourceTracker) this.animationId = 0 this.canvasContainer = document.querySelector(canvasSelector); //初始化 this.init(cssCanvas); this.animate(); window.addEventListener('resize', this.resizeRenderer.bind(this)); // 添加滚动事件,鼠标滚动模型执行动画 // window.addEventListener('wheel', this.wheelRenderer.bind(this)); } init(cssCanvas?) { // 初始化场景 this.initScene(); // 初始化环境光 this.initLight(); // 初始化相机 this.initCamera(); //初始化渲染器 this.initRenderer(); // 初始化控制器 this.initControles(); if(cssCanvas){ this.initCSSRenderer(cssCanvas) // this.addTextSprite() } // this.setTestPlane() } initScene() { this.scene = this.track(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 = this.track(new THREE.AmbientLight(0xffffff, 1)); light.position.set(0, 1000, 1000); (this.scene as THREE.Scene).add(light); } initCamera() { this.camera = this.track(new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 1000)); this.camera?.position.set(0, 0.2, 0.3); // const helper = new THREE.CameraHelper( this.camera); // this.scene?.add( helper ); } initRenderer() { this.renderer = this.track(new THREE.WebGLRenderer({ antialias: true, alpha:true })) as THREE.WebGLRenderer; // 设置屏幕像素比 this.renderer?.setPixelRatio(window.devicePixelRatio); // 设置渲染的尺寸 this.renderer?.setSize(window.innerWidth, window.innerHeight); // 色调映射 this.renderer.toneMapping = THREE.ACESFilmicToneMapping; this.renderer.outputEncoding = THREE.sRGBEncoding; this.renderer.shadowMap.enabled = true this.renderer.shadowMap.type = THREE.PCFSoftShadowMap // 曝光程度 this.renderer.toneMappingExposure = 1; this.canvasContainer.appendChild(this.renderer.domElement); } initCSSRenderer(cssCanvas) { this.CSSCanvasContainer = document.querySelector(cssCanvas); this.css3dRender = this.track(new CSS3DRenderer()) as CSS3DRenderer this.css3dRender.setSize((window.innerWidth), (window.innerHeight)) this.CSSCanvasContainer?.appendChild(this.css3dRender.domElement) this.css3dRender.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera) this.orbitControls = this.track(new OrbitControls(this.camera as THREE.Camera, this.css3dRender?.domElement)) as OrbitControls; this.orbitControls.update() // this.orbitControls.enableZoom = false; // // to disable rotation // this.orbitControls.enableRotate = false; // // to disable pan // this.orbitControls.enablePan = false; } initControles() { this.orbitControls = this.track(new OrbitControls(this.camera as THREE.Camera, this.renderer?.domElement)) as OrbitControls; this.orbitControls.update() // this.orbitControls.enableZoom = false; // // to disable rotation // this.orbitControls.enableRotate = false; // // to disable pan // this.orbitControls.enablePan = false; } setModalCenter(group) { var box3 = new THREE.Box3() // 计算层级模型group的包围盒 // 模型group是加载一个三维模型返回的对象,包含多个网格模型 box3.expandByObject(group) // 计算一个层级模型对应包围盒的几何体中心在世界坐标中的位置 var 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 }; setModel(modalName) { const a = new Date().getTime() / 1000 return new Promise(async (resolve, reject) => { try { const db = window['CustomDB'] const modalArr = await db.modal.where('modalName').equals(modalName).toArray() // debugger if(modalArr.length > 0) { const modalValue = modalArr[0].modalVal try { const gltfLoader = new GLTFLoader() const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath( '/3D/draco/gltf/' ); dracoLoader.setDecoderConfig({ type: "js" }); //使用兼容性强的draco_decoder.js解码器 dracoLoader.preload(); gltfLoader.setDRACOLoader(dracoLoader); gltfLoader.setPath('/3d/glft/') gltfLoader.parse(modalValue, '/3d/glft/', (gltf) => { const object = this.track(gltf.scene.children[0]) this.setModalCenter(object) object.traverse((obj) => { if (obj instanceof THREE.Mesh) { obj.material.emissiveIntensity = 1 obj.material.emissiveMap = obj.material.map if(obj.name !== "buxiugangse") { obj.receiveShadow = true } obj.castShadow = true } else if (obj.type === 'Object3D') { // const text3D = this.addYFText(obj) // const textCQ3D = this.addCQText(obj) // gltf.scene.add(text3D) // gltf.scene.add(textCQ3D) } }) this.scene?.add(object); console.log(object); resolve(object) }, (err)=> { console.log(err); }) } catch (error) { console.log(error); } } } catch (error) { reject('加载模型出错') } }) } setTestPlane() { const plane = new THREE.Mesh( new THREE.PlaneGeometry( 100, 100 ), 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 ); } /* 自定义材质 */ setCustomMaterial(group){} /* 场景环境背景 */ setEnvMap(hdr) { // new RGBELoader().setPath('/public/3D/hdr/').load(hdr + '.jpeg', (texture) => { // debugger // texture.mapping = THREE.EquirectangularReflectionMapping; // (this.scene as THREE.Scene).background = texture; // (this.scene as THREE.Scene).environment = texture; // }); new THREE.TextureLoader().setPath('/3D/hdr/').load(hdr + '.jpeg', (texture) => { texture.mapping = THREE.EquirectangularReflectionMapping; // (this.scene as THREE.Scene).background = new THREE.Color('#00000000'); (this.scene as THREE.Scene).environment = texture; }); } render() { const delta = this.clock.getElapsedTime(); this.resetLookAt() this.startMY() 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.animationMixer?.update(delta); } /* 实时物体刷新朝向 */ resetLookAt() { } /* 漫游 */ startMY() {} animate() { // this.renderer?.setAnimationLoop(this.render.bind(this)); if(this.animationId != -1) { this.render() this.animationId = requestAnimationFrame(this.animate.bind(this)) } } resizeRenderer() { // 更新相机比例 (this.camera as THREE.PerspectiveCamera).aspect = window.innerWidth / window.innerHeight; // 刷新相机矩阵 this.camera?.updateProjectionMatrix(); // 设置场景尺寸 this.renderer?.setSize(window.innerWidth, window.innerHeight); this.css3dRender?.setSize(window.innerWidth, window.innerHeight); } 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); } deleteModal() { try { this.resourceTracker && this.resourceTracker.dispose() this.renderer?.dispose() this.renderer?.forceContextLoss() // this.renderer?.domElement = null this.renderer = null // this.css3dRender?.domElement = null this.css3dRender = null; cancelAnimationFrame(this.animationId); const gl = this.renderer?.domElement.getContext("webgl"); gl && gl?.getExtension("WEBGL_lose_context")?.loseContext(); const gl1 = this.css3dRender?.domElement.getContext("webgl"); gl1 && gl1?.getExtension("WEBGL_lose_context")?.loseContext(); this.animationId = -1 this.resourceTracker?.untrack(this.resourceTracker.resources) // this.scene = null console.log('销毁场景',this.scene); } catch (error) { console.log(error); } // this.scene?.children.forEach((obj:any) => { // if(obj.type === 'Group'){ // obj.traverse(function(item:any) { // if (item.type === 'Mesh') { // item.geometry.dispose(); // item.material.dispose(); // !!item.clear&&item.clear(); // } // }) // }else if (obj.material) { // obj.material.dispose(); // }else if (obj.geometry) { // obj.geometry.dispose(); // } // this.scene?.remove(obj) // !!obj.clear??obj.clear() // obj = null; // }) // let gl = this.renderer?.domElement.getContext("webgl"); // gl && gl?.getExtension("WEBGL_lose_context")?.loseContext(); // this.renderer?.forceContextLoss(); // this.renderer?.dispose(); // this.camera = null; // this.orbitControls = null; // this.renderer.domElement = null; // this.renderer = null; // css3dRender.domElement = null; // css3dRender = null; // model3D.innerHTML = ''; // css3D.innerHTML = ''; // model3D = null // css3D = null // this.stats = null // scene = null THREE.Cache.clear(); console.log('3D环境已清理干净'); } /* 创建字体精灵 */ addTextSprite(message, parameters) { if (parameters === undefined) parameters = {}; var fontface = parameters.hasOwnProperty("fontface") ? parameters["fontface"] : "Arial"; /* 字体大小 */ var fontsize = parameters.hasOwnProperty("fontsize") ? parameters["fontsize"] : 18; /* 边框厚度 */ var borderThickness = parameters.hasOwnProperty("borderThickness") ? parameters["borderThickness"] : 4; /* 边框颜色 */ var borderColor = parameters.hasOwnProperty("borderColor") ? parameters["borderColor"] : { r: 0, g: 0, b: 0, a: 1.0 }; /* 背景颜色 */ var backgroundColor = parameters.hasOwnProperty("backgroundColor") ? parameters["backgroundColor"] : { r: 255, g: 255, b: 255, a: 1.0 }; /* 创建画布 */ var canvas = document.createElement('canvas'); var context = canvas.getContext('2d') as CanvasRenderingContext2D; // canvas.width = 0 // canvas.height = 200 /* 字体加粗 */ context.font = "Bold " + fontsize + "px " + fontface; /* 获取文字的大小数据,高度取决于文字的大小 */ var metrics = context.measureText(message); var textWidth = metrics.width; /* 背景颜色 */ context.fillStyle = "rgba(" + backgroundColor.r + "," + backgroundColor.g + "," + backgroundColor.b + "," + backgroundColor.a + ")"; /* 边框的颜色 */ context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + "," + borderColor.b + "," + borderColor.a + ")"; context.lineWidth = borderThickness; /* 绘制圆角矩形 */ this.roundRect(context, borderThickness / 2, borderThickness / 2, textWidth + borderThickness, fontsize * 1.4 + borderThickness, 6); // this.roundRect(context, borderThickness / 2, borderThickness / 2, 10000, 10000, 6); /* 字体颜色 */ context.fillStyle = "rgba(0, 0, 0, 1.0)"; context.fillText(message, borderThickness, fontsize + borderThickness); // context.fillRect(borderThickness / 2, borderThickness / 2, 10000, 10000) /* 画布内容用于纹理贴图 */ var texture = new THREE.Texture(canvas); texture.needsUpdate = true; var spriteMaterial = new THREE.SpriteMaterial({ map: texture, transparent:false }); var sprite = new THREE.Sprite(spriteMaterial); /* 缩放比例 */ sprite.scale.set(10, 5, 1); return sprite; } /* 绘制圆角矩形 */ roundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.quadraticCurveTo(x + w, y, x + w, y + r); ctx.lineTo(x + w, y + h - r); ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); ctx.lineTo(x + r, y + h); ctx.quadraticCurveTo(x, y + h, x, y + h - r); ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath(); ctx.fill(); ctx.stroke(); } } export default UseThree;