新增场景释放机制和Dispose方法
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "material-editor",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "1.0.2",
 | 
			
		||||
  "version": "1.0.3",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,6 @@ async function textureRenderUpdate(textureRecord:TextureTableRecord){
 | 
			
		||||
 */
 | 
			
		||||
export class MaterialEditor extends Singleton
 | 
			
		||||
{
 | 
			
		||||
    private _pointerLocked = false;
 | 
			
		||||
 | 
			
		||||
    Geometrys: Map<string, Geometry | BufferGeometry>;
 | 
			
		||||
 | 
			
		||||
    CurGeometryName = "球";
 | 
			
		||||
@@ -38,6 +36,7 @@ export class MaterialEditor extends Singleton
 | 
			
		||||
    ShowObject: Object3D;
 | 
			
		||||
    ShowMesh: Mesh;
 | 
			
		||||
    Viewer: Viewer;
 | 
			
		||||
    CameraControl?: MaterialEditorCameraControl;
 | 
			
		||||
 | 
			
		||||
    private _MeshMaterial: MeshPhysicalMaterial = new MeshPhysicalMaterial({});
 | 
			
		||||
    //构造
 | 
			
		||||
@@ -72,7 +71,7 @@ export class MaterialEditor extends Singleton
 | 
			
		||||
            // this.Viewer.PreViewer.Cursor.CursorObject.visible = false;
 | 
			
		||||
            // this.Viewer.UsePass = false;
 | 
			
		||||
            this.initScene();
 | 
			
		||||
            new MaterialEditorCameraControl(this.Viewer, this.ShowObject.position);
 | 
			
		||||
            this.CameraControl = new MaterialEditorCameraControl(this.Viewer, this.ShowObject.position);
 | 
			
		||||
            this.Viewer.ZoomAll();
 | 
			
		||||
 | 
			
		||||
            // 初始化相机位置到观察物体的正后方
 | 
			
		||||
@@ -82,6 +81,9 @@ export class MaterialEditor extends Singleton
 | 
			
		||||
            this.Viewer.UpdateRender();
 | 
			
		||||
            this.Viewer.Fov = 90;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            console.warn("Viewer has already been initialized");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    SetViewer(canvas: HTMLElement)
 | 
			
		||||
    {
 | 
			
		||||
@@ -186,8 +188,24 @@ export class MaterialEditor extends Singleton
 | 
			
		||||
        this.Update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dispose()
 | 
			
		||||
    Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        this.Geometrys.clear();
 | 
			
		||||
        this.Geometrys = undefined;
 | 
			
		||||
        this.Canvas = undefined;
 | 
			
		||||
        // object在Viewer中进行释放
 | 
			
		||||
        this.ShowObject = undefined;
 | 
			
		||||
        this.ShowMesh = undefined;
 | 
			
		||||
 | 
			
		||||
        this.CameraControl?.dispose();
 | 
			
		||||
        this.Viewer?.Dispose();
 | 
			
		||||
        this.CameraControl = undefined;
 | 
			
		||||
        this.Viewer = undefined;
 | 
			
		||||
        this.exrPromise = undefined;
 | 
			
		||||
        this.exrTexture?.dispose();
 | 
			
		||||
        this.exrTexture = undefined;
 | 
			
		||||
        this.metaTexture?.dispose();
 | 
			
		||||
        this.metaTexture = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async Update()
 | 
			
		||||
 
 | 
			
		||||
@@ -24,12 +24,24 @@ export class MaterialEditorCameraControl {
 | 
			
		||||
 | 
			
		||||
    initMouseControl() {
 | 
			
		||||
        let el = this.Viewer.Renderer.domElement;
 | 
			
		||||
        el.addEventListener("pointerdown", (e) => { this.pointId = e.pointerId; }, false);
 | 
			
		||||
        el.addEventListener("pointerdown", this.onPointerDown, false);
 | 
			
		||||
        el.addEventListener("mousedown", this.onMouseDown, false);
 | 
			
		||||
        el.addEventListener("mousemove", this.onMouseMove, false);
 | 
			
		||||
        el.addEventListener("mouseup", this.onMouseUp, false);
 | 
			
		||||
        el.addEventListener('wheel', this.onMouseWheel, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dispose() {
 | 
			
		||||
        const el = this.Viewer.Renderer.domElement;
 | 
			
		||||
        el.removeEventListener("pointerdown", this.onPointerDown);
 | 
			
		||||
        el.removeEventListener("mousedown", this.onMouseDown);
 | 
			
		||||
        el.removeEventListener("mousemove", this.onMouseMove);
 | 
			
		||||
        el.removeEventListener("mouseup", this.onMouseUp);
 | 
			
		||||
        el.removeEventListener('wheel', this.onMouseWheel, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onPointerDown = (event: PointerEvent) => { this.pointId = event.pointerId; };
 | 
			
		||||
 | 
			
		||||
    onMouseDown = (event: MouseEvent) => {
 | 
			
		||||
        this.requestPointerLock();
 | 
			
		||||
        this._MouseIsDown = true;
 | 
			
		||||
@@ -49,14 +61,11 @@ export class MaterialEditorCameraControl {
 | 
			
		||||
            else this.Viewer.Rotate(this._movement);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    onMouseWheel = (event: WheelEvent) =>
 | 
			
		||||
    {
 | 
			
		||||
        if (event.deltaY < 0)
 | 
			
		||||
        {
 | 
			
		||||
    onMouseWheel = (event: WheelEvent) => {
 | 
			
		||||
        if (event.deltaY < 0) {
 | 
			
		||||
            this.Viewer.Zoom(0.6);
 | 
			
		||||
        }
 | 
			
		||||
        else if (event.deltaY > 0)
 | 
			
		||||
        {
 | 
			
		||||
        else if (event.deltaY > 0) {
 | 
			
		||||
            this.Viewer.Zoom(1.4);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,11 @@
 | 
			
		||||
 | 
			
		||||
let instanceMap = new Map();
 | 
			
		||||
 | 
			
		||||
export interface PrototypeType<T> extends Function
 | 
			
		||||
{
 | 
			
		||||
export interface PrototypeType<T> extends Function {
 | 
			
		||||
    prototype: T;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ConstructorFunctionType<T = any> extends PrototypeType<T>
 | 
			
		||||
{
 | 
			
		||||
export interface ConstructorFunctionType<T = any> extends PrototypeType<T> {
 | 
			
		||||
    new(...args: any[]): T;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -23,13 +21,11 @@ export type ConstructorType<T = unknown, Static extends Record<string, any> = Pr
 | 
			
		||||
 *     //获得单例
 | 
			
		||||
 *     let a = A.GetInstance();
 | 
			
		||||
 */
 | 
			
		||||
export class Singleton
 | 
			
		||||
{
 | 
			
		||||
export class Singleton {
 | 
			
		||||
    protected constructor() { }
 | 
			
		||||
 | 
			
		||||
    //ref:https://github.com/Microsoft/TypeScript/issues/5863
 | 
			
		||||
    static GetInstance<T extends Singleton>(this: ConstructorType<T, typeof Singleton>): T
 | 
			
		||||
    {
 | 
			
		||||
    static GetInstance<T extends Singleton>(this: ConstructorType<T, typeof Singleton>): T {
 | 
			
		||||
        if (instanceMap.has(this))
 | 
			
		||||
            return instanceMap.get(this);
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
@@ -37,4 +33,23 @@ export class Singleton
 | 
			
		||||
        instanceMap.set(this, __instance__);
 | 
			
		||||
        return __instance__;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * 释放指定类型的单例实例。
 | 
			
		||||
    * @returns {boolean} 如果实例存在并被释放则返回 true,否则返回 false。
 | 
			
		||||
    */
 | 
			
		||||
    static ReleaseInstance<T extends Singleton>(this: ConstructorType<T, typeof Singleton>): boolean {
 | 
			
		||||
        if (instanceMap.has(this)) {
 | 
			
		||||
            const instance = instanceMap.get(this);
 | 
			
		||||
            // 如果实例有清理方法,可以在这里调用
 | 
			
		||||
            if (typeof (instance as any).dispose === 'function') {
 | 
			
		||||
                (instance as any).dispose();
 | 
			
		||||
            }
 | 
			
		||||
            instanceMap.delete(this);
 | 
			
		||||
            console.log(`Singleton instance of ${this.name} has been released.`);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        console.log(`No singleton instance of ${this.name} found to release.`);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,25 @@
 | 
			
		||||
import { Raycaster, Scene, Vector3, WebGLRenderer, type Vector, type WebGLRendererParameters } from "three";
 | 
			
		||||
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;//渲染器  //暂时只用这个类型
 | 
			
		||||
    m_DomEl: HTMLElement;    //画布容器
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
@@ -28,90 +33,95 @@ export class Viewer {
 | 
			
		||||
        this.NeedUpdate = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    UpdateRender()
 | 
			
		||||
    {
 | 
			
		||||
    UpdateRender() {
 | 
			
		||||
        this.NeedUpdate = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param {HTMLElement} canvasContainer 可以传入一个div或者一个画布
 | 
			
		||||
     * @memberof Viewer
 | 
			
		||||
     */
 | 
			
		||||
    constructor(canvasContainer: HTMLElement) {
 | 
			
		||||
 | 
			
		||||
        this.m_DomEl = canvasContainer;
 | 
			
		||||
        this.initRender(canvasContainer);
 | 
			
		||||
        this.OnSize();
 | 
			
		||||
        this.StartRender();
 | 
			
		||||
        window.addEventListener("resize", () => {
 | 
			
		||||
            this.OnSize();
 | 
			
		||||
        });
 | 
			
		||||
        this.SetSize(); // 初始尺寸设置
 | 
			
		||||
        this.StartRender(); // 开始渲染循环
 | 
			
		||||
 | 
			
		||||
        // 注意: OnSize 是一个箭头函数属性,它的 this 上下文是固定的
 | 
			
		||||
        // 因此可以直接用于添加和移除事件监听器
 | 
			
		||||
        window.addEventListener("resize", this.OnResize);
 | 
			
		||||
        this.InitCamera();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //初始化render
 | 
			
		||||
    initRender(canvasContainer: HTMLElement) {
 | 
			
		||||
        let params: WebGLRendererParameters = {
 | 
			
		||||
            antialias: true,//antialias:true/false是否开启反锯齿
 | 
			
		||||
            precision: "highp",//precision:highp/mediump/lowp着色精度选择
 | 
			
		||||
            alpha: true//alpha:true/false是否可以设置背景色透明
 | 
			
		||||
            antialias: true,
 | 
			
		||||
            precision: "highp",
 | 
			
		||||
            alpha: true
 | 
			
		||||
        };
 | 
			
		||||
        this.Renderer = new WebGLRenderer(params);
 | 
			
		||||
        //加到画布
 | 
			
		||||
        canvasContainer.appendChild(this.Renderer.domElement);
 | 
			
		||||
 | 
			
		||||
        this.Renderer.autoClear = true;
 | 
			
		||||
 | 
			
		||||
        //如果设置,那么它希望所有的纹理和颜色都是预乘的伽玛。默认值为false。
 | 
			
		||||
        // this.Renderer.gammaInput = true;
 | 
			
		||||
        // this.Renderer.gammaOutput = true;
 | 
			
		||||
        // this.Renderer.shadowMap.enabled = true;
 | 
			
		||||
        // this.Renderer.toneMapping = ReinhardToneMapping;
 | 
			
		||||
        //设置设备像素比。 这通常用于HiDPI设备,以防止模糊输出画布。
 | 
			
		||||
        this.Renderer.setPixelRatio(window.devicePixelRatio);
 | 
			
		||||
        this.Renderer.physicallyCorrectLights = true;
 | 
			
		||||
        //this.Renderer.toneMappingExposure = Math.pow(1, 5.0); // to allow for very bright scenes.
 | 
			
		||||
 | 
			
		||||
        //设置它的背景色为黑色
 | 
			
		||||
        this.Renderer.physicallyCorrectLights = true; //  r150 后默认 false, 根据需要设置
 | 
			
		||||
        this.Renderer.setClearColor(0xffffff, 1);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        this.OnSize();
 | 
			
		||||
        // this.OnSize(); // 在构造函数中调用,这里可以不用重复
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InitCamera() {
 | 
			
		||||
        this.m_Camera.CameraType = CameraType.PerspectiveCamera;
 | 
			
		||||
        this.m_Camera.Near = 0.001;
 | 
			
		||||
        this.m_Camera.Camera.far = 1000000000;
 | 
			
		||||
        if (this.m_Camera.Camera) { // 确保 CameraUpdate 内部的 Camera 已初始化
 | 
			
		||||
            this.m_Camera.Camera.far = 1000000000;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    OnSize = (width?: number, height?: number) => {
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
        //校验.成为2的倍数 避免外轮廓错误.
 | 
			
		||||
        if (this._Width % 2 == 1)
 | 
			
		||||
            this._Width -= 1;
 | 
			
		||||
        if (this._Height % 2 == 1)
 | 
			
		||||
            this._Height -= 1;
 | 
			
		||||
        if (this._Width % 2 === 1) this._Width -= 1;
 | 
			
		||||
        if (this._Height % 2 === 1) this._Height -= 1;
 | 
			
		||||
 | 
			
		||||
        this.Renderer.setSize(this._Width, this._Height);
 | 
			
		||||
        this.m_Camera.SetSize(this._Width, this._Height);
 | 
			
		||||
        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 = () => {
 | 
			
		||||
        requestAnimationFrame(this.StartRender);
 | 
			
		||||
        if (this._Scene != null && this.NeedUpdate) {
 | 
			
		||||
        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();
 | 
			
		||||
            this.m_Camera.Update();
 | 
			
		||||
            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();
 | 
			
		||||
@@ -123,9 +133,10 @@ export class Viewer {
 | 
			
		||||
            }
 | 
			
		||||
            , this.m_Camera.Camera
 | 
			
		||||
        );
 | 
			
		||||
        plan.intersectRay(raycaster.ray, pt, true);
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
@@ -135,51 +146,133 @@ export class Viewer {
 | 
			
		||||
        pt.y = - (pt.y * heightHalf) + heightHalf;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新视角观测目标(物体中心)
 | 
			
		||||
     *
 | 
			
		||||
     * @memberof Viewer
 | 
			
		||||
     */
 | 
			
		||||
    UpdateLockTarget() {
 | 
			
		||||
        let renderList = this.Renderer.renderLists.get(this._Scene, this.m_Camera.Camera);
 | 
			
		||||
        let box = GetBoxArr(renderList.opaque.map(o => o.object));
 | 
			
		||||
        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
 | 
			
		||||
            this.m_LookTarget = cZeroVec;
 | 
			
		||||
            // @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()
 | 
			
		||||
    {
 | 
			
		||||
        this.m_Camera.ZoomExtentsBox3(GetBox(this._Scene, 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.");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    </CfFlex>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { onMounted, ref, useTemplateRef } from 'vue';
 | 
			
		||||
import { onBeforeUnmount, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
 | 
			
		||||
import MaterialAdjuster from './MaterialAdjuster.vue';
 | 
			
		||||
import { useScene } from '../stores/sceneStore';
 | 
			
		||||
import CfFlex from './CfFlex.vue';
 | 
			
		||||
@@ -21,15 +21,26 @@ const textureSrc = ref(config.textureSrc);
 | 
			
		||||
 | 
			
		||||
// 禁用右键菜单
 | 
			
		||||
document.addEventListener('contextmenu', (e) => e.preventDefault()); 
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    scene.Initial(container.value);
 | 
			
		||||
    eventbus.Subscribe('submit', () => adjusterRef.value.Upload());
 | 
			
		||||
    eventbus.Subscribe('update-texture', () => {
 | 
			
		||||
        textureSrc.value = config.textureSrc
 | 
			
		||||
    });
 | 
			
		||||
    eventbus.Subscribe('submit', HandleUpload);
 | 
			
		||||
    eventbus.Subscribe('update-texture', HandleChangeTexture);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
    eventbus.Unsubscribe('submit', HandleUpload);
 | 
			
		||||
    eventbus.Unsubscribe('update-texture', HandleChangeTexture);
 | 
			
		||||
    scene.Dispose();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function HandleUpload() {
 | 
			
		||||
    adjusterRef.value.Upload();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function HandleChangeTexture() {
 | 
			
		||||
    textureSrc.value = config.textureSrc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								src/helpers/helper.three.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/helpers/helper.three.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import type { Material, Texture, Object3D, Mesh, Line, Points } from "three";
 | 
			
		||||
 | 
			
		||||
/** 深度释放材质和纹理 */
 | 
			
		||||
export function disposeMaterial(material: Material | Material[]): void {
 | 
			
		||||
    if (Array.isArray(material)) {
 | 
			
		||||
        material.forEach(mat => disposeSingleMaterial(mat));
 | 
			
		||||
    } else if (material) { // 确保 material 不是 undefined 或 null
 | 
			
		||||
        disposeSingleMaterial(material);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function disposeSingleMaterial(material: Material): void {
 | 
			
		||||
    material.dispose();
 | 
			
		||||
    // 遍历材质的属性,查找并释放纹理
 | 
			
		||||
    for (const key in material) {
 | 
			
		||||
        const value = (material as any)[key] as unknown; // 使用 unknown 来进行更安全的类型检查
 | 
			
		||||
        if (value && typeof value === 'object' && 'isTexture' in value && (value as Texture).isTexture) {
 | 
			
		||||
            (value as Texture).dispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 释放节点的几何体、材质和纹理 */
 | 
			
		||||
export function disposeNode(node: Object3D): void {
 | 
			
		||||
    // 类型守卫,检查节点是否为 Mesh, Line, 或 Points
 | 
			
		||||
    if ((node as Mesh).isMesh || (node as Line).isLine || (node as Points).isPoints) {
 | 
			
		||||
        const obj = node as Mesh | Line | Points;
 | 
			
		||||
 | 
			
		||||
        if (obj.geometry) {
 | 
			
		||||
            obj.geometry.dispose();
 | 
			
		||||
            // console.log("Disposed geometry:", obj.geometry.uuid);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (obj.material) {
 | 
			
		||||
            disposeMaterial(obj.material);
 | 
			
		||||
            // console.log("Disposed material(s) for node:", obj.uuid);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,7 +12,11 @@ export const useEvent = defineStore('event', () => {
 | 
			
		||||
        events[name]?.forEach(e => e(...args));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return { Subscribe, Publish };
 | 
			
		||||
    function Unsubscribe(name: EventNames, callback: Function) {
 | 
			
		||||
        events[name] = events[name]?.filter(e => e !== callback) || [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return { Subscribe, Publish, Unsubscribe };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export type EventNames = 'submit' | 'update-texture'
 | 
			
		||||
@@ -8,7 +8,7 @@ import { materialRenderer } from "../common/MaterialRenderer";
 | 
			
		||||
import { MaterialOut } from "../common/MaterialSerializer";
 | 
			
		||||
 | 
			
		||||
export const useScene = defineStore('scene', () => {
 | 
			
		||||
    let _editor: MaterialEditor;
 | 
			
		||||
    let _editor: MaterialEditor | undefined;
 | 
			
		||||
    const _currGeometry = ref<string>('球');
 | 
			
		||||
    const _currTexture = ref<Texture>();
 | 
			
		||||
    const CurrGeometry = computed({
 | 
			
		||||
@@ -37,11 +37,22 @@ export const useScene = defineStore('scene', () => {
 | 
			
		||||
 | 
			
		||||
        const view = _editor.Viewer;
 | 
			
		||||
        window.onresize = () => {
 | 
			
		||||
            view.OnSize(canvas.clientWidth, canvas.clientHeight);
 | 
			
		||||
            view.SetSize(canvas.clientWidth, canvas.clientHeight);
 | 
			
		||||
            view.UpdateRender();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function Dispose() {
 | 
			
		||||
        console.log("Disposing...");
 | 
			
		||||
        Material.value.GoodBye();
 | 
			
		||||
        _editor?.Dispose();
 | 
			
		||||
        _editor = undefined;
 | 
			
		||||
        window.onresize = undefined;
 | 
			
		||||
        
 | 
			
		||||
        // 释放Singleton
 | 
			
		||||
        MaterialEditor.ReleaseInstance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function ChangeGeometry(geoName: string) {
 | 
			
		||||
        _currGeometry.value = geoName;
 | 
			
		||||
        let geo = _editor.Geometrys.get(_currGeometry.value);
 | 
			
		||||
@@ -121,7 +132,8 @@ export const useScene = defineStore('scene', () => {
 | 
			
		||||
        ChangeTextureAsync,
 | 
			
		||||
        UpdateTexture,
 | 
			
		||||
        SerializeMaterialAsync,
 | 
			
		||||
        GenerateMaterialLogoAsync
 | 
			
		||||
        GenerateMaterialLogoAsync,
 | 
			
		||||
        Dispose
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user