material-editor/src/common/Viewer.ts

278 lines
9.8 KiB
TypeScript

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.");
}
}