import { Raycaster, Scene, Vector3, WebGLRenderer, type WebGLRendererParameters, Object3D, Mesh, Line, Points, Material, Texture } from "three"; import { cZeroVec, GetBox, GetBoxArr } from "./GeUtils"; import { PlaneExt } from "./PlaneExt"; import { CameraType, CameraUpdate } from "webcad_ue4_api"; import { disposeNode } from "../helpers/helper.three"; export class Viewer { m_LookTarget: any; m_Camera: CameraUpdate = new CameraUpdate(); protected NeedUpdate: boolean = true; Renderer!: WebGLRenderer; // 在 initRender 中初始化,使用 ! 断言 m_DomEl: HTMLElement; _Height: number = 0; _Width: number = 0; _Scene: Scene = new Scene(); private m_IsDisposed: boolean = false; // 添加一个标志来停止渲染循环和防止重复释放 private m_AnimationFrameId: number | null = null; // 用于取消动画帧 get Scene() { return this._Scene; } get Fov() { return this.m_Camera.Fov; } set Fov(val: number) { this.m_Camera.Fov = val; this.NeedUpdate = true; } UpdateRender() { this.NeedUpdate = true; } constructor(canvasContainer: HTMLElement) { this.m_DomEl = canvasContainer; this.initRender(canvasContainer); this.SetSize(); // 初始尺寸设置 this.StartRender(); // 开始渲染循环 // 注意: OnSize 是一个箭头函数属性,它的 this 上下文是固定的 // 因此可以直接用于添加和移除事件监听器 window.addEventListener("resize", this.OnResize); this.InitCamera(); } initRender(canvasContainer: HTMLElement) { let params: WebGLRendererParameters = { antialias: true, precision: "highp", alpha: true }; this.Renderer = new WebGLRenderer(params); canvasContainer.appendChild(this.Renderer.domElement); this.Renderer.autoClear = true; this.Renderer.setPixelRatio(window.devicePixelRatio); this.Renderer.physicallyCorrectLights = true; // r150 后默认 false, 根据需要设置 this.Renderer.setClearColor(0xffffff, 1); // this.OnSize(); // 在构造函数中调用,这里可以不用重复 } InitCamera() { this.m_Camera.CameraType = CameraType.PerspectiveCamera; this.m_Camera.Near = 0.001; if (this.m_Camera.Camera) { // 确保 CameraUpdate 内部的 Camera 已初始化 this.m_Camera.Camera.far = 1000000000; } } OnResize = (evt: UIEvent) => this.SetSize(); SetSize = (width?: number, height?: number) => { if (this.m_IsDisposed || !this.m_DomEl) return; // 如果已销毁或容器不存在,则不执行 this._Width = width ? width : this.m_DomEl.clientWidth; this._Height = height ? height : this.m_DomEl.clientHeight; if (this._Width % 2 === 1) this._Width -= 1; if (this._Height % 2 === 1) this._Height -= 1; if (this.Renderer) { this.Renderer.setSize(this._Width, this._Height); } if (this.m_Camera) { this.m_Camera.SetSize(this._Width, this._Height); } this.NeedUpdate = true; // 尺寸变化后需要重新渲染 }; StartRender = () => { if (this.m_IsDisposed) { if (this.m_AnimationFrameId !== null) { cancelAnimationFrame(this.m_AnimationFrameId); this.m_AnimationFrameId = null; } return; } this.m_AnimationFrameId = requestAnimationFrame(this.StartRender); if (this._Scene && this.NeedUpdate) { // 确保 _Scene 存在 this.Render(); if (this.m_Camera) { // 确保 m_Camera 存在 this.m_Camera.Update(); } this.NeedUpdate = false; } }; Render() { if (this.m_IsDisposed || !this.Renderer || !this._Scene || !this.m_Camera || !this.m_Camera.Camera) { return; } this.Renderer.render(this._Scene, this.m_Camera.Camera); } // ScreenToWorld, WorldToScreen, UpdateLockTarget, Rotate, Pan, Zoom, etc. methods remain the same... // ... (省略了您提供的其他方法,它们与 dispose 无直接关系,保持不变) ... ScreenToWorld(pt: Vector3, planVec?: Vector3) { if (this.m_IsDisposed || !this._Scene || !this.m_Camera || !this.m_Camera.Camera) return; //变换和求交点 let plan = new PlaneExt(planVec || new Vector3(0, 0, 1)); let raycaster = new Raycaster(); // 射线从相机射线向屏幕点位置 raycaster.setFromCamera( { x: (pt.x / this._Width) * 2 - 1, y: - (pt.y / this._Height) * 2 + 1 } , this.m_Camera.Camera ); plan.intersectRay(raycaster.ray, pt, true); // 假设 intersectRay 修改 pt } WorldToScreen(pt: Vector3) { if (this.m_IsDisposed || !this.m_Camera || !this.m_Camera.Camera) return; let widthHalf = this._Width * 0.5; let heightHalf = this._Height * 0.5; pt.project(this.m_Camera.Camera); pt.x = (pt.x * widthHalf) + widthHalf; pt.y = - (pt.y * heightHalf) + heightHalf; } UpdateLockTarget() { if (this.m_IsDisposed || !this.Renderer || !this._Scene || !this.m_Camera || !this.m_Camera.Camera) return; // @ts-ignore access to private renderLists const renderList = this.Renderer.renderLists.get(this._Scene, this.m_Camera.Camera); // @ts-ignore const box = GetBoxArr(renderList.opaque.map(o => o.object)); // 确保 GetBoxArr 定义正确 if (box) // @ts-ignore this.m_LookTarget = box.getCenter(new Vector3()); else // @ts-ignore this.m_LookTarget = cZeroVec; // 确保 cZeroVec 定义正确 } Rotate(mouseMove: Vector3) { if (this.m_IsDisposed || !this.m_Camera) return; this.m_Camera.Rotate(mouseMove, this.m_LookTarget); this.NeedUpdate = true; } RotateAround(movement: Vector3, center: Vector3) { if (this.m_IsDisposed || !this.m_Camera) return; this.m_Camera.Rotate(movement, center); this.NeedUpdate = true; } Pan(mouseMove: Vector3) { if (this.m_IsDisposed || !this.m_Camera) return; this.m_Camera.Pan(mouseMove); this.NeedUpdate = true; } Zoom(scale: number, center?: Vector3) { if (this.m_IsDisposed || !this.m_Camera) return; this.m_Camera.Zoom(scale, center); this.NeedUpdate = true; } ZoomAll() { if (this.m_IsDisposed || !this.m_Camera || !this._Scene) return; // @ts-ignore this.m_Camera.ZoomExtentsBox3(GetBox(this._Scene, true)); // 确保 GetBox 定义正确 this.NeedUpdate = true; } ViewToTop() { if (this.m_IsDisposed || !this.m_Camera) return; this.m_Camera.LookAt(new Vector3(0, 0, -1)); this.NeedUpdate = true; } ViewToFront() { if (this.m_IsDisposed || !this.m_Camera) return; this.m_Camera.LookAt(new Vector3(0, 1, 0)); this.NeedUpdate = true; } ViewToSwiso() { if (this.m_IsDisposed || !this.m_Camera) return; this.m_Camera.LookAt(new Vector3(1, 1, -1)); this.NeedUpdate = true; } /** * 销毁 Viewer 实例并释放所有相关资源。 */ public Dispose(): void { if (this.m_IsDisposed) { return; // 防止重复释放 } console.log("Disposing Viewer..."); // 1. 停止渲染循环 this.m_IsDisposed = true; // 设置标志,渲染循环将在下一帧停止 if (this.m_AnimationFrameId !== null) { cancelAnimationFrame(this.m_AnimationFrameId); this.m_AnimationFrameId = null; } // 2. 移除事件监听器 window.removeEventListener("resize", this.OnResize); // 如果 m_DomEl 上有其他直接添加的事件监听器,也应在此移除 // 3. 销毁场景内容 (几何体、材质、纹理) if (this._Scene) { // 遍历场景中的所有对象并释放它们的资源 this._Scene.traverse((object) => { disposeNode(object); }); // 从场景中移除所有子对象 // scene.clear() 也可以,但在此之前我们已经遍历并释放了资源 while (this._Scene.children.length > 0) { const child = this._Scene.children[0]; // 确保子对象的资源已通过上面的 traverse 处理 this._Scene.remove(child); } // console.log("Scene cleared and resources disposed."); } // 4. 销毁渲染器 if (this.Renderer) { // @ts-ignore access to private renderLists if (this.Renderer.renderLists) { // 检查 renderLists 是否存在 // @ts-ignore this.Renderer.renderLists.dispose(); // console.log("Renderer renderLists disposed."); } this.Renderer.dispose(); // console.log("Renderer disposed."); // 从 DOM 中移除渲染器的 canvas 元素 if (this.Renderer.domElement && this.Renderer.domElement.parentElement) { this.Renderer.domElement.parentElement.removeChild(this.Renderer.domElement); // console.log("Renderer DOM element removed."); } } // 5. 清除引用 (帮助垃圾回收) // @ts-ignore this._Scene = null; // @ts-ignore this.Renderer = null; // @ts-ignore this.m_DomEl = null; // @ts-ignore this.m_Camera = null; // @ts-ignore this.m_LookTarget = null; console.log("Viewer disposed successfully."); } }