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

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;
}
}