UNPKG

bd-admin

Version:

一款能根据需求快速配置vue后台管理的脚手架

493 lines (477 loc) 18.1 kB
import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; // "three": "^0.160.0", // 渲染组合器 import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"; // 渲染通道 import { RenderPass } from "three/addons/postprocessing/RenderPass.js"; // ShaderPass功能:使用后处理Shader创建后处理通道 import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js"; // 发光描边 import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js"; // SMAA抗锯齿通道 import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js"; // 伽马校正 import { GammaCorrectionShader } from "three/examples/jsm/shaders/GammaCorrectionShader.js"; import { Params, Vector3, FnRenderType } from "./interface/index"; import { CSS2DRenderer } from "three/addons/renderers/CSS2DRenderer.js"; import { CSS3DRenderer } from "three/addons/renderers/CSS3DRenderer.js"; import { RGBELoader } from "three/addons/loaders/RGBELoader.js"; import { MoveTo } from "./utils/index"; class Raycaster { protected readonly event: any; protected readonly camera: THREE.PerspectiveCamera; constructor(event: any, camera: THREE.PerspectiveCamera) { this.event = event; this.camera = camera; } init() { const raycaster = new THREE.Raycaster( this.camera.position, this.normalize(), ); raycaster.camera = this.camera; return raycaster; } protected normalize = () => { const vector = new THREE.Vector3( (this.event.offsetX / window.innerWidth) * 2 - 1, -(this.event.offsetY / window.innerHeight) * 2 + 1, 0.5, ); //三维坐标对象 vector.unproject(this.camera); return vector.sub(this.camera.position).normalize(); }; } class Controls { public readonly controls: any; constructor( camera: THREE.PerspectiveCamera, domElement: HTMLCanvasElement, params: Params, ) { const controls = new OrbitControls(camera, domElement); const { enableZoom = true, zoomSpeed = 0.5, enableDamping = true, maxDistance = 1000, minDistance = 30, rotateSpeed = 0.5, maxPolarAngle = Math.PI / 2, maxAzimuthAngle = Math.PI / 4, minAzimuthAngle = -Math.PI / 4, } = params.controls || {}; controls.enableZoom = enableZoom; // 启用或禁用摄像机的缩放。 controls.zoomSpeed = zoomSpeed; // 摄像机缩放的速度 controls.enableDamping = enableDamping; //启用阻尼 controls.maxDistance = maxDistance ? maxDistance : params.far; // 相机向外移动多少 controls.minDistance = minDistance; // 相机向内移动多少 controls.rotateSpeed = rotateSpeed; //旋转的速度 controls.maxPolarAngle = maxPolarAngle; //垂直旋转的角度的上限,范围是0到Math.PI controls.maxAzimuthAngle = maxAzimuthAngle; //水平旋转的角度的上限,范围是-Math.PI到Math.PI(或Infinity无限制) controls.minAzimuthAngle = minAzimuthAngle; //水平旋转的角度的下限,范围是-Math.PI到Math.PI(或-Infinity无限制), controls.screenSpacePanning = false; this.controls = controls; } } export default class Thre3d { protected readonly params: Params; public scene: THREE.Scene; // 网格 public camera: THREE.PerspectiveCamera | null; // 相机 public renderer: THREE.WebGLRenderer | null; // WebGL渲染器 protected directionalLight: THREE.DirectionalLight | null; // 模拟太阳光 private fnEvent: Map<FnRenderType, Function>; // 事件类型函数 public width: number; public height: number; protected controls: any; // 轨道控制器 protected composer: any; protected dome: HTMLElement | null; protected cameraPositionsFn: Function | null; protected cameraLookAtFn: Function | null; labelRenderer: any; css3Renderer: any; constructor(params: Params) { this.params = params; this.cameraPositionsFn = null; this.cameraLookAtFn = null; this.width = 0; this.height = 0; this.labelRenderer = null; this.css3Renderer = null; this.dome = null; this.directionalLight = null; this.fnEvent = new Map<FnRenderType, Function>(); this.controls = null; this.composer = null; this.renderer = null; this.camera = null; this.scene = new THREE.Scene(); this.#init(); } #init() { this.dome = document.querySelector(`#${this.params.id}`); if (!this.dome) return; this.width = this.dome.clientWidth; //宽度 this.height = this.dome.clientHeight; //高度 this.camera = new THREE.PerspectiveCamera( 30, this.width / this.height, 1, this.params.far ? this.params.far : 10000, ); this.renderer = new THREE.WebGLRenderer({ alpha: true, //渲染器透明 antialias: true, //抗锯齿 precision: "highp", //着色器开启高精度 logarithmicDepthBuffer: true, // 是否使用对数深度缓存 }); this.renderer.render(this.scene, this.camera); //执行渲染操作 this.scene.fog = new THREE.Fog(0xffffff, 400, 10000); this.dome.addEventListener("click", this.#onMouseClick, false); //点击事件 // WebGL渲染器 const renderer = this.renderer; //开启HiDPI设置 renderer.setPixelRatio(window.devicePixelRatio); renderer.shadowMap.enabled = true; renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1; renderer.outputColorSpace = THREE.SRGBColorSpace; this.dome.appendChild(renderer.domElement); const labelRenderer = new CSS2DRenderer(); labelRenderer.domElement.style.position = "absolute"; // 相对标签原位置位置偏移大小 labelRenderer.domElement.style.top = "0px"; labelRenderer.domElement.style.left = "0px"; labelRenderer.domElement.style.pointerEvents = "none"; this.labelRenderer = labelRenderer; this.dome.appendChild(labelRenderer.domElement); // 创建一个CSS3渲染器CSS3DRenderer const css3Renderer = new CSS3DRenderer(); // HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合 css3Renderer.domElement.style.position = "absolute"; css3Renderer.domElement.style.top = "0px"; //设置.pointerEvents=none,解决HTML元素标签对threejs canvas画布鼠标事件的遮挡 css3Renderer.domElement.style.pointerEvents = "none"; this.css3Renderer = css3Renderer; this.dome.appendChild(css3Renderer.domElement); this.initControls(); this.#reset(); this.#setScene(); } // 添加发光通道 addEffectComposer = () => { const effectColorSpaceConversion = new ShaderPass(GammaCorrectionShader); const composer = new EffectComposer(this.renderer); const renderPass = new RenderPass(this.scene, this.camera); composer.addPass(renderPass); const outlinePass = new OutlinePass( new THREE.Vector2(this.width, this.height), this.scene, this.camera, ); //模型描边颜色,默认白色 outlinePass.visibleEdgeColor.set(0xffff00); //高亮发光描边厚度 outlinePass.edgeThickness = 4; //高亮描边发光强度 outlinePass.edgeStrength = 6; //模型闪烁频率控制,默认0不闪烁 outlinePass.pulsePeriod = 2; composer.addPass(outlinePass); const pixelRatio = this.renderer!.getPixelRatio(); // SMAA抗锯齿通道 const smaaPass = new SMAAPass( this.width * pixelRatio, this.height * pixelRatio, ); composer.addPass(smaaPass); this.renderer!.domElement.style.touchAction = "none"; this.renderer!.domElement.addEventListener("pointermove", onPointerMove); const mouse = new THREE.Vector2(); let selectedObjects: THREE.Object3D[] = []; function onPointerMove(event: any) { if (event.isPrimary === false) return; mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; checkIntersection(); } const raycaster = new THREE.Raycaster(); const checkIntersection = () => { raycaster.setFromCamera(mouse, this.camera!); const intersects = raycaster.intersectObject(this.scene, true); if (intersects.length > 0) { const selectedObject = intersects[0].object; addSelectedObject(selectedObject); outlinePass.selectedObjects = selectedObjects; } else { // outlinePass.selectedObjects = []; } }; function addSelectedObject(object: THREE.Object3D) { selectedObjects = []; selectedObjects.push(object); } composer.addPass(effectColorSpaceConversion); this.composer = composer; }; #setScene = () => { this.scene.background = new THREE.Color(0x333333); // this.scene.fog = new THREE.Fog(0x333333, 10, 15); this.scene.environment = new RGBELoader().load( "https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr", ); this.scene.environment.mapping = THREE.EquirectangularReflectionMapping; // 天空 // 注释,有需要自己添加背景图 // this.scene.background = new THREE.CubeTextureLoader() // .setPath("/texture/sky/") // .load([ // "sky.left.jpg", // "sky.right.jpg", // "sky.top.jpg", // "sky.bottom.jpg", // "sky.back.jpg", // "sky.front.jpg", // ]); // const AxesHelper = new THREE.AxesHelper(10000); // AxesHelper.name = "辅助坐标系"; }; //点击事件 #onMouseClick = (event: any) => { event.preventDefault(); const raycaster = new Raycaster(event, this.camera!).init(); const intersects = raycaster.intersectObjects(this.scene.children); const selected = intersects[0]; //intersects是射线沿着摄像机机镜头的方向穿过的所有物体,这里取第一个物体 if (!selected) return; const { object: { type }, } = selected; const x = Math.floor(selected.point.x * 100) / 100; const y = Math.floor(selected.point.y * 100) / 100; const z = Math.floor(selected.point.z * 100) / 100; console.log("x:" + x + ",y:" + y + ",z:" + z); let returnFlag = false; let sFn, mFn; switch (type) { case "Sprite": sFn = this.fnEvent.get("spriteClick"); if (sFn) sFn(selected); returnFlag = true; console.log("精灵图", selected); break; case "Mesh": mFn = this.fnEvent.get("modelClick"); if (mFn) mFn(selected); break; default: console.log("暂未定义的类型", type); } if (returnFlag) return; const cFn = this.fnEvent.get("click"); if (cFn) cFn(new THREE.Vector3(x, y, z)); }; #onMouseMove = (event: any) => { event.preventDefault(); const raycaster = new Raycaster(event, this.camera!).init(); const intersects = raycaster.intersectObjects(this.scene.children); const selected = intersects[0]; //intersects是射线沿着摄像机机镜头的方向穿过的所有物体,这里取第一个物体 if (!selected) return; const x = Math.floor(selected.point.x * 1000) / 1000; const y = Math.floor(selected.point.y * 1000) / 1000; const z = Math.floor(selected.point.z * 1000) / 1000; const address = new THREE.Vector3(x, y, z); const mFn = this.fnEvent.get("mouseMove"); if (mFn) mFn(address); return address; }; render = () => { if (this.renderer) { this.renderer.render(this.scene, this.camera!); //执行渲染操作 } if (this.composer) { this.composer.render(this.scene, this.camera); } if (this.labelRenderer) { this.labelRenderer.render(this.scene, this.camera); } if (this.css3Renderer) { this.css3Renderer.render(this.scene, this.camera); } if (this.controls) { this.controls.update(); } if (this.cameraPositionsFn) { this.cameraPositionsFn(); } if (this.cameraLookAtFn) { this.cameraLookAtFn(); } if ( this.directionalLight && this.params.shadow && this.params.timeMultiply ) { const r = Date.now() * (0.0001 / this.params.timeMultiply); this.directionalLight.position.x = 700 * Math.cos(r); this.directionalLight.position.y = 700 * Math.sin(r); // const angle: number = // (new THREE.Vector2( // this.directionalLight.position.x, // this.directionalLight.position.y, // ).angle() * // 180) / // Math.PI; // if (1 < angle && angle < 180) { // console.log("白天", angle); // } else { // console.log("夜晚", angle); // } } }; resize = () => { this.dome = document.querySelector(`#${this.params.id}`); if (!this.dome) return; this.width = this.dome.clientWidth; //宽度 this.height = this.dome.clientHeight; //高度 this.#reset(); }; #reset = () => { this.camera!.aspect = this.width / this.height; this.camera!.updateProjectionMatrix(); this.renderer!.setSize(this.width, this.height); this.labelRenderer.setSize(this.width, this.height); this.css3Renderer.setSize(this.width, this.height); }; /** 平行光 * @param {Vector3} position 坐标 * @param {boolean} intensity 光线强度 */ addSun(position: Vector3 | undefined, intensity: number | undefined = 2.5) { const { x = 2000, y = 40000, z = 2000 } = position || {}; if (this.params.shadow) { const directionalLight = new THREE.DirectionalLight(0xffffff, intensity); // 新建一个平行光源,颜色未白色,强度为1 directionalLight.position.set(x, y, z); // 将此平行光源调整到一个合适的位置 directionalLight.castShadow = true; // 将此平行光源产生阴影的属性打开 const d = 100; //阴影范围 // 设置平行光的的阴影属性,即一个长方体的长宽高,在设定值的范围内的物体才会产生阴影 directionalLight.shadow.camera.left = -d; directionalLight.shadow.camera.right = d; directionalLight.shadow.camera.top = d; directionalLight.shadow.camera.bottom = -d; directionalLight.shadow.camera.near = 20; directionalLight.shadow.camera.far = 8000; directionalLight.shadow.mapSize.x = 2048; // 定义阴影贴图的宽度和高度,必须为2的整数此幂 directionalLight.shadow.mapSize.y = 2048; // 较高的值会以计算时间为代价提供更好的阴影质量 directionalLight.shadow.bias = -0.0005; //解决条纹阴影的出现 this.directionalLight = directionalLight; directionalLight.name = "sunShadow"; this.scene.add(directionalLight); // 将此平行光源加入场景中,我们才可以看到这个光源 } else { const group = new THREE.Group(); group.name = "sun"; const directionalLight1 = new THREE.DirectionalLight( 0xffffff, intensity / 4, ); directionalLight1.position.set(x, y, z); group.add(directionalLight1); this.scene.add(group); } } // 注册事件 on(type: FnRenderType, fn: Function) { this.fnEvent.set(type, fn); } // 移除事件 removeOn(type: FnRenderType) { const m = this.fnEvent.get(type); if (m) this.fnEvent.delete(type); } /** * 切换视角 * @param {Vector3} position position目的坐标 * @param {boolean} animation 是否启动动画 * @param {Function} callback 到达目的回调函数 */ setCameraPositions = ( position: Vector3, animation?: boolean, callback?: Function, ) => { if (!animation) { this.cameraPositionsFn = null; this.camera!.position.set(position.x, position.y, position.z); if (callback) callback(); return; } const endPosition = new THREE.Vector3(position.x, position.y, position.z); // 结束位置 const M = new MoveTo(this.camera!.position, endPosition, 5); const target = JSON.parse(JSON.stringify(this.controls.target)); this.cameraPositionsFn = () => { this.controls.target.set(0, 0, 0); this.controls.update(); const manhattanDistanceTo = M.move(); this.controls.target.set(target.x, target.y, target.z); this.controls.update(); if (manhattanDistanceTo < 1) { this.cameraPositionsFn = null; } // }; }; /**切换注视点 * @param {Vector3} position position目的坐标 * @param {boolean} animation 是否启动动画 * @param {Function} callback 到达目的回调函数 * @returns */ setCameraLookAt = ( position: Vector3, animation?: boolean, callback?: Function, ) => { if (!animation) { if (!this.camera) return; this.cameraLookAtFn = null; this.camera.lookAt(position.x, position.y, position.z); if (!this.controls) return; this.controls.target.set(position.x, position.y, position.z); if (callback) callback(); return; } const endPosition = new THREE.Vector3(position.x, position.y, position.z); // 结束位置 const M = new MoveTo(this.controls.target, endPosition, 30); this.cameraLookAtFn = () => { const manhattanDistanceTo = M.move(); if (manhattanDistanceTo < 1) { this.controls.target.copy(endPosition); this.cameraLookAtFn = null; if (callback) callback(); } }; }; removeControls() { if (!this.controls) return; this.controls.object = null; this.controls = null; } initControls() { if (!this.camera || !this.renderer) return; const { cameraPosition: { x = 0, y = 100, z = 200 }, lookAt, } = this.params; const { x: lookAtX = 0, y: lookAtY = 0, z: lookAtZ = 0 } = lookAt || {}; // 设置相机控件轨道控制器OrbitControls const controls = new Controls( this.camera, this.renderer.domElement, this.params, ).controls; controls.target.set(lookAtX, lookAtY, lookAtZ); this.controls = controls; this.camera.lookAt(lookAtX, lookAtY, lookAtZ); this.camera.position.set(x, y, z); this.controls.update(); } }