278 lines
9.8 KiB
TypeScript
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.");
|
|
}
|
|
} |