You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
salary-management-oneself/src/pages/demo/threejs/modules/Viewer/index.ts

248 lines
8.9 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<THREE.Vector3>;
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;
}
}