import * as THREE from "three"; import { AmbientLight, AxesHelper, Camera, PerspectiveCamera, Raycaster, Scene, SRGBColorSpace, Vector2, WebGLRenderer } from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js"; import { Tween } from "three/examples/jsm/libs/tween.module.js"; import Stats from "three/examples/jsm/libs/stats.module.js"; import Events from "./Events"; import { threeStore } from "@/store"; export type Animate = { fun: (arg: any) => any; content: any; }; export default class Viewer { public id: string; public viewerDom!: HTMLElement; public renderer!: WebGLRenderer; public scene!: Scene; public camera!: PerspectiveCamera; public controls!: OrbitControls; public css2Renderer: CSS2DRenderer | undefined; public raycaster!: Raycaster; public mouse!: Vector2; public isDestroy = false; // 是否销毁 public animateEventList = new Map(); public statsControls!: Stats; // 性能监测 public raycasterObjects: THREE.Object3D[] = []; public tween!: Tween; public mouseEvent: MouseEvent | undefined; constructor(id: string) { this.id = id; this.initViewer(); } private initViewer() { this.initRenderer(); //创建渲染器 this.initScene(); //创建场景 this.initLight(); // 创建光源 this.initCamera(); // 创建相机 this.initControl(); // 创建控制器 // this.initCss2Renderer(); // 创建css2渲染器 this.raycaster = new Raycaster(); // 创建射线 this.mouse = new Vector2(); // 创建鼠标位置 const animate = () => { if (this.isDestroy) return; requestAnimationFrame(animate); // TWEEN.update(); // 更新动画 // this.css2Renderer?.render(this.scene, this.camera); this.updateDom(); this.renderDom(); // 全局的公共动画函数,添加函数可同步执行 this.animateEventList.forEach((event) => { // console.log("animateEventList"); if (event.fun && event.content) event.fun(event.content); }); }; animate(); } private initRenderer() { // 获取画布dom this.viewerDom = document.getElementById(this.id) as HTMLElement; // 初始化渲染器 this.renderer = new WebGLRenderer({ logarithmicDepthBuffer: true, //true/false用于启用或禁用对数深度缓冲区。 antialias: true, // true/false表示是否开启反锯齿 alpha: true, // true/false 表示是否可以设置背景色透明 precision: "mediump", // highp/mediump/lowp 表示着色精度选择 premultipliedAlpha: true // true/false 表示是否可以设置像素深度(用来度量图像的分辨率) // preserveDrawingBuffer: false, // true/false 表示是否保存绘图缓冲 // physicallyCorrectLights: true, // true/false 表示是否开启物理光照 }); this.renderer.clearDepth(); //清除深度缓冲区。在渲染之前,这通常用于重置深度缓冲区,以确保正确的深度测试 // 开启模型对象的局部剪裁平面功能 // 如果不设置为true,设置剪裁平面的模型不会被剪裁 this.renderer.localClippingEnabled = true; this.renderer.shadowMap.enabled = true; this.renderer.outputColorSpace = SRGBColorSpace; // 可以看到更亮的材质,同时这也影响到环境贴图。 this.viewerDom.appendChild(this.renderer.domElement); } private initScene() { this.scene = new Scene(); } private initLight() { const ambient = new AmbientLight(0xffffff, 0.6); this.scene.add(ambient); const light = new THREE.DirectionalLight(0xffffff); light.position.set(0, 200, 100); light.castShadow = true; light.shadow.camera.top = 180; light.shadow.camera.bottom = -100; light.shadow.camera.left = -120; light.shadow.camera.right = 400; light.shadow.camera.near = 0.1; light.shadow.camera.far = 400; // 设置mapSize属性可以使阴影更清晰,不那么模糊 light.shadow.mapSize.set(1024, 1024); light.name = "initLight"; this.scene.add(light); } private initCamera() { // 渲染相机 this.camera = new PerspectiveCamera(25, this.viewerDom.clientWidth / this.viewerDom.clientHeight, 1, 2000); //设置相机位置 // this.camera.position.set(5, 1, -5); this.camera.position.set(4, 2, -3); //设置相机方向 this.camera.lookAt(0, 0, 0); } private initControl() { this.controls = new OrbitControls(this.camera as Camera, this.renderer?.domElement); this.controls.enabled = true; // 控制器关 this.controls.enableDamping = false; this.controls.screenSpacePanning = false; // 定义平移时如何平移相机的位置 控制不上下移动 this.controls.minDistance = 2; this.controls.maxDistance = 1000; this.controls.addEventListener("change", () => { this.renderer.render(this.scene, this.camera); }); } private initCss2Renderer() { this.css2Renderer = new CSS2DRenderer(); } private updateDom() { this.controls.update(); // // 更新参数 this.camera.aspect = this.viewerDom.clientWidth / this.viewerDom.clientHeight; // 摄像机视锥体的长宽比,通常是使用画布的宽/画布的高 this.camera.updateProjectionMatrix(); // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用,来使得这些改变生效 this.renderer.setSize(this.viewerDom.clientWidth, this.viewerDom.clientHeight); this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比 } // 渲染dom private renderDom() { this.renderer?.render(this.scene as Scene, this.camera as Camera); } private statsUpdate(statsControls: any) { statsControls.update(); } /**坐标轴辅助 */ public addAxis() { const axis = new AxesHelper(1000); //创建坐标轴辅助对象 this.scene?.add(axis); } public addAnimate(id: string, animate: Animate) { this.animateEventList.set(id, animate); } public addStats() { if (!this.statsControls) this.statsControls = new Stats(); this.statsControls.dom.style.position = "absolute"; this.viewerDom.appendChild(this.statsControls.dom); // 添加到动画 this.addAnimate("stats", { fun: this.statsUpdate, content: this.statsControls }); } /**自定义鼠标事件触发的范围,给定一个模型组,对给定的模型组鼠标事件才生效 */ public setRaycasterObjects(objList: THREE.Object3D[]): void { this.raycasterObjects = objList; } /**注册鼠标事件监听 */ public initRaycaster() { this.raycaster = new Raycaster(); const initRaycasterEvent: Function = (eventName: keyof HTMLElementEventMap): void => { //这里的container就是画布所在的div,也就是说,这个是要拿整个scene所在的容器来界定的 let getBoundingClientRect = this.viewerDom.getBoundingClientRect(); let offsetWidth = this.viewerDom.offsetWidth; let offsetHeight = this.viewerDom.offsetHeight; const funWrap = _.throttle((event: any) => { this.mouseEvent = { ...event, //真正的鼠标相对于画布的位置 x: event.clientX - getBoundingClientRect.left, y: event.clientY - getBoundingClientRect.top }; this.mouse.x = ((event.clientX - getBoundingClientRect.left) / offsetWidth) * 2 - 1; this.mouse.y = -((event.clientY - getBoundingClientRect.top) / offsetHeight) * 2 + 1; threeStore.setRaycasterIntersectObjects(this.getRaycasterIntersectObjects()); }, 100); this.viewerDom.addEventListener(eventName, funWrap, false); }; initRaycasterEvent("click"); // initRaycasterEvent("dblclick"); // initRaycasterEvent("mousemove"); } // 获取射线与物体相交的物体 private getRaycasterIntersectObjects(): THREE.Intersection[] { // 如果射线与物体相交的数组为空,则返回空数组 if (!this.raycasterObjects.length) return []; // 设置射线从相机发出,经过鼠标位置 this.raycaster.setFromCamera(this.mouse, this.camera); // 返回射线与物体相交的数组 return this.raycaster.intersectObjects(this.raycasterObjects, true); } public addCameraTween(targetPosition = new THREE.Vector3(1, 1, 1), duration = 1000, onComplete: () => void) { this.initCameraTween(); this.tween.to(targetPosition, duration).start().onComplete(onComplete); } /** * 初始化补间动画库tween */ public initCameraTween() { if (!this.camera) return; this.tween = new Tween(this.camera.position); } public destroy() { this.scene.traverse((child: any) => { if (child.material) { child.material.dispose(); } if (child.geometry) { child.geometry.dispose(); } child = null; }); this.renderer.forceContextLoss(); this.renderer.dispose(); this.scene.clear(); this.isDestroy = true; } }